/**
 * @version	1.2.0
 *   Change Log:
 *    - 0.5.0: first ui framework prototype : Leemin
 *    - 0.6.2: Updated Menu class that resize by itself after added or removed : J.J. Kim
 *    - 0.6.3: Fixed bug on Menu for Android and BlackBerry
 *    - 0.6.4a: Remove menu style attributes : J.J. Kim
 *    - 0.6.5: Fixed minor bug on CheckBox : J.J. Kim
 *    - 0.6.6: Add setOnClickHandler on ToolBarItem : J.J. Kim
 *    - 0.6.7: Fix bug for scroll : J.M. Lee
 *    - 0.6.8: Fix setBackgroundImage: S.M. Yang
 *    - 0.6.9: add SetLabel for checkbox: S.M. Yang
 *    - 0.6.10: tabbar, checkbox 수정
 *    - 0.6.11: Android 에서 두번 클릭현상 해결: Seyoung Jang
 *    - 1.0.0: Removed logs: J.J. Kim
 *    - 1.1.0: Refactoring all source code. Base component structure is improved. Removed unused component. support dynamic component creation. etc....: Leemin (2011.4.6) 
 *    - 1.1.0a: Fixed bug in setBackgroundImage : JJ Kim (2011.4.18)
 *    - 1.1.1: added animation Tweener  : Leemin (2011.4.25)   
 *    - 1.1.2: added Locale  : Leemin (2011.4.29)
 *    - 1.1.8: changed Component.getOuterHeight, ScrollView.setAutoResizeHeight...applied woori, metlife poc result : Leemin (2011.5.24)
 *    - 1.2.0: Add Formatter Class for setData on Components...applied kyobo poc result : J.J. Kim (2011.6.14)
 * */

/*******************************************************************************
 * Framework General
 ******************************************************************************/
var global = this;
var msg = {};

Function.prototype.inherits = function inherits(parentFunction) {
	this.prototype = new parentFunction;
	this.prototype.parent = parentFunction;
	this.prototype.constructor = this;
	return this;
};

var ComponentManager = function() {
};

ComponentManager.getComponentClass = function getComponentClass(className) {
	return window[className];
};

var Delegate = function Delegator() {

};

Delegate.create = function create(that, thatMethod, args) {
	return function() {
		return thatMethod.apply(that, args);
	};
};

var UIManager = function UIManager() {
	this.componentList = {};
	this.isTouch = ('ontouchstart' in window);
};

UIManager.prototype.getComponentById = function getComponentById(id) {
	if (this.componentList[id] != undefined) {
		return this.componentList[id];
	} else {
		var element = document.getElementById(id);
		if (element == null) {
			return null;
		} else {
			if (this.componentList[id] != null)
				this.componentList[id].remove();
			this.componentList[id] = new (ComponentManager
					.getComponentClass(element.title))(element.id, element.name);
			return this.componentList[id];
		}
	}
};
var uimanager = new UIManager();

/*******************************************************************************
 * Component
 ******************************************************************************/

/**
 * Component 는 UI 상에 보이는 모든 컴퍼넌트들의 베이스 클래스로서 모든 UI 컴퍼넌트들은 컴퍼넌트의 메소드를 포함합니다. 디폴트
 * 생성자로서 컴퍼넌트를 생성합니다.
 * 
 * @param tagID
 * @param dataMapId
 * @constructor
 */
var Component = function Component(id, dataMapId) {
	if (id == undefined)
		return;

	this.uimanager = uimanager;
	this.id = id;
	this.dataMapId = dataMapId;
	this.componentName = arguments.callee.caller.name;

	this.draggable = false;
	this.data = null;

	this.onClickHandler = null;

	this.element = document.getElementById(id);
	if (this.element == null) {
		this.element = this.createElement();
	}
	this.element.component = this;
	if (this.element.parentNode != null) {
		this.parent = this.element.parentNode.component;
	}

	this.setEventType();
	this.initialize();

	this.formatter = null;
};

Component.prototype.initialize = function initialize() {
};

Component.prototype.setEventType = function setEventType() {
	this.startEvent = (uimanager.isTouch) ? "touchstart" : "mousedown";
	this.moveEvent = (uimanager.isTouch) ? "touchmove" : "mousemove";
	this.endEvent = (uimanager.isTouch) ? "touchend" : "mouseup";
};

/**
 * 컴퍼넌트에 새로운 이벤트 핸들러를 등록한다. ex) component.addEventListener("click",
 * handler_name, true)
 * 
 * @param eventName
 *            이벤트의 이름. touchstart, touchend 등등
 * @param handler
 * @param useCapture
 *            이벤트 처리시 capture 사용할 지 여부
 */
Component.prototype.addEventListener = function addEventListener(eventName,
		handler, useCapture) {
	var that = this;
	if (this.handlerList == undefined)
		this.handlerList = [];
	this.handlerList.push({
		eventName : eventName,
		delegator : function() {
			return handler.apply(that, arguments);
		},
		handler : handler
	});
	this.element.addEventListener(eventName,
			this.handlerList[this.handlerList.length - 1].delegator,
			(useCapture == undefined) ? false : useCapture);
};

/**
 * 이벤트 핸들러를 제거합니다.
 * 
 * @param eventName
 * @param handler
 */
Component.prototype.removeEventListener = function removeEventListener(
		eventName, handler) {
	if (this.handlerList == undefined)
		return;
	var cnt = this.handlerList.length;
	for ( var i = 0; i < cnt; i++) {
		if (this.handlerList[i].eventName == eventName
				&& this.handlerList[i].handler == handler) {
			this.element.removeEventListener(eventName,
					this.handlerList[i].delegator);
			return;
		}
	}
};

/**
 * 특정 이벤트를 발생시킵니다.
 * 
 * @param eventObj
 */
Component.prototype.dispatchEvent = function dispatchEvent(eventObj) {
	this.element.dispatchEvent(eventObj);
};

/**
 * 새로운 컴퍼넌트를 생성하고 반환합니다.
 * 
 * @returns 생성된 컴퍼넌트
 */
Component.prototype.createElement = function createElement() {
	var el = document.createElement("div");
	el.className = this.componentName;

	return el;
};

/**
 * 현재 컴퍼넌트가 가리키고 있는 HTML 엘리먼트를 반환합니다.
 * 
 * @returns
 */
Component.prototype.getElement = function getElement() {
	return this.element;
};

/**
 * 컴퍼넌트의 스타일을 지정합니다.
 * 
 * @param prop
 *            스타일 이름
 * @param value
 *            스타일 값
 */
Component.prototype.setStyle = function setStyle(prop, value) {
	if (prop.indexOf("width") > -1 || prop.indexOf("height") > -1
			|| prop.indexOf("left") > -1 || prop.indexOf("right") > -1
			|| prop.indexOf("top") > -1 || prop.indexOf("bottom") > -1
			|| prop.indexOf("padding") > -1 || prop.indexOf("margin") > -1) {
		if (!isNaN(value))
			value = value + "px";
	}
	;
	this.element.style[prop] = value;
};

/**
 * 컴퍼넌트에 지정된 스타일들을 리턴합니다.
 * 
 * @param prop
 * @returns
 */
Component.prototype.getStyle = function getStyle(prop) {
	var y = "";

	if (this.element.currentStyle)
		y = this.element.currentStyle[prop];
	else if (window.getComputedStyle)
		y = document.defaultView.getComputedStyle(this.element, null)
				.getPropertyValue(prop);
	return y;
};

/**
 * 컴퍼넌트를 삭제합니다.
 * 
 * @param prop
 */
Component.prototype.remove = function remove(prop) {
	var event = document.createEvent("Event");
	event.initEvent("removed", true, false);
	event.component = this;
	this.dispatchEvent(event);

	this.element.parentNode.removeChild(this.element);
	delete this;
};

/**
 * 컴퍼넌트에 인자로 주어진 로케일 메시지를 지정합니다. 데이터는 DOM 엘리먼트의 innerHTML 속성값에 지정됩니다.
 * 
 * @param value.
 *            innerHTML 속성에 들어갈 데이터
 * 
 */
Component.prototype.setLocaleMessage = function setLocaleMessage(messege) {
	if (window.isSimulator)
		return;
	this.i18n = device.locale;
	this.setData(messege[device.locale]);
};

/**
 * 컴퍼넌트에 인자로 주어진 데이터를 지정합니다. 데이터는 DOM 엘리먼트의 innerHTML 속성값에 지정됩니다.
 * 
 * @param value.
 *            innerHTML 속성에 들어갈 데이터
 */
Component.prototype.setData = function setData(data) {
	if (data == undefined)
		data = "";
	this.data = data;

	if (this.formatter == null) // Formatter 가 없는 경우
		this.element.innerHTML = data;
	else
		this.element.innerHTML = this.formatter.format(data);
};

/**
 * 컴퍼넌트 DOM 엘리먼트의 innerHTML 속성값을 리턴합니다.
 * 
 * @returns String innerHTML 값
 */
Component.prototype.getData = function getData(data) {
	return this.element.innerHTML;
};

/**
 * 컴퍼넌트에 인자로 주어진 데이터를 지정합니다. 데이터는 DOM 엘리먼트의 innerHTML 속성값에 지정됩니다.
 * 
 * @param value.
 *            스타일 클래스
 */
Component.prototype.setStyleClass = function setStyleClass(className) {
	this.element.className = className;
};

/**
 * CSS 스타일 클래스 이름을 리턴합니다.
 * 
 * @returns CSS 클래스 이름
 */
Component.prototype.getStyleClass = function getStyleClass() {
	return this.element.className;
};

/**
 * CSS display 속성값을 설정합니다.
 * 
 * @param value
 *            'none', 'block' 등 display 속성값
 */
Component.prototype.setDisplay = function setDisplay(display) {
	this.element.style.display = display;
};

/**
 * 현재 설정된 CSS display 속성값을 리턴합니다.
 * 
 * @return string CSS display 속성값
 * 
 */
Component.prototype.getDisplay = function getDisplay() {
	return this.getStyle("display");
};
/**
 * CSS Visibility 속성값을 설정합니다. (true/false)
 * 
 * @param value
 *            Boolean 값 (true/false)
 */
Component.prototype.setVisibility = function setVisibility(visibility) {
	return this.element.style.visibility = visibility;
};

/**
 * 현재 설정된 CSS Visibility 속성 값을 리턴합니다.
 * 
 * @returns Boolean 값 (true/false). 보여지면 true, 안보이면 false.
 */
Component.prototype.getVisibility = function getVisibility() {
	return this.getStyle("visibility");
};
/**
 * 화면 스크린 상단에서부터 컴퍼넌트까지의 높이 값을 리턴합니다.
 * 
 * @return 화면 스크린 상단에서부터 컴퍼넌트까지의 높이 값
 */
Component.prototype.getOffsetHeight = function getOffsetHeight() {
	return this.element.offsetHeight;
};

/**
 * 컴퍼넌트의 offsetWidth 속성값을 리턴합니다.
 * 
 * @returns offsetWidth 값
 */
Component.prototype.getOffsetWidth = function getOffsetWidth() {
	return this.element.offsetHeight;
};

/**
 * 컴퍼넌트의 offsetTop 속성값을 리턴합니다.
 * 
 * @returns offsetTop 값
 */
Component.prototype.getOffsetTop = function getOffsetLeft() {
	return this.element.offsetTop;
};

/**
 * 컴퍼넌트의 offsetLeft 속성값을 리턴합니다.
 * 
 * @returns offsetLeft 값
 */
Component.prototype.getOffsetLeft = function getOffsetLeft() {
	return this.element.offsetLeft;
};

/**
 * 컴퍼넌트의 offsetBottom 속성값을 리턴합니다.
 * 
 * @returns offsetBottom 값
 */
Component.prototype.getOffsetBottom = function getOffsetBottom() {
	return this.element.offsetButtom;
};

/**
 * 컴퍼넌트의 offsetRight 속성값을 리턴합니다.
 * 
 * @returns offsetRight 값
 */
Component.prototype.getOffsetRight = function getOffsetRight() {
	return this.element.offsetRight;
};

/**
 * 컴퍼넌트의 높이를 지정합니다.
 * 
 * @param value
 *            컴퍼넌트 높이 값
 */
Component.prototype.setHeight = function(height) {
	if (!isNaN(height))
		height = height + "px";
	return this.element.style.height = height;
};

/**
 * 컴퍼넌트의 높이 값을 리턴합니다.
 * 
 * @return 컴퍼넌트의 높이 값
 */
Component.prototype.getHeight = function getHeight() {
	return this.getStyle("height");
};

/**
 * 컴퍼넌트의 너비 값을 설정합니다.
 * 
 * @param width
 *            너비값
 * @returns
 */
Component.prototype.setWidth = function(width) {
	if (!isNaN(width))
		width = width + "px";
	return this.element.style.width = width;
};

/**
 * 컴퍼넌트의 너비값을 리턴합니다.
 * 
 * @returns
 */
Component.prototype.getWidth = function getWidth() {
	return this.getStyle("width");
};

/**
 * 컴퍼넌트의 너비값을 Number로 리턴합니다. 히든
 * 
 * @returns
 */
Component.prototype.getWidthNumber = function getWidthNumber() {
	return Number(this.getStyle("width").split("px").join(""));
};

/**
 * 클릭 이벤트 핸들러를 등록합니다.
 * 
 * @param handler
 *            컴퍼넌트가 클릭되었을 시 실행되는 함수
 * 
 */
Component.prototype.setOnClickHandler = function setOnClickHandler(handler) {
	return this.element.onclick = handler;
};

/**
 * 컴퍼넌트의 글꼴 색상을 지정합니다.
 * 
 * @param color
 */
Component.prototype.setColor = function setColor(color) {
	this.element.style.color = color;
};

/**
 * 컴퍼넌트의 경계선을 설정합니다.
 * 
 * @param wid
 *            경계선 굵기
 * @param color
 *            경계선 색상
 * @param style
 *            경계선 타입(solid, dotted)
 * @param radius
 *            경계선 모서리
 */
Component.prototype.setBorder = function setBorder(wid, color, style, radius) {
	this.element.style.borderStyle = (style == undefined) ? "solid" : style;
	this.element.style.borderWidth = wid;
	this.element.style.borderColor = color;
	this.element.style.borderRadius = radius;
};

/**
 * 컴퍼넌트의 배경색상을 설정합니다.
 * 
 * @param color
 *            색상(red, #000000)
 */
Component.prototype.setBackgroundColor = function setBackgroundColor(color) {
	this.element.style.backgroundColor = color;
};

/**
 * 컴퍼넌트의 배경 이미지를 설정합니다.
 * 
 * @param path
 *            이미지 경로
 */
Component.prototype.setBackgroundImage = function setBackgroundImage(path) {
	this.element.style.backgroundImage = "url(" + path + ")";
};

/**
 * 컴퍼넌트 내의 글을 설정합니다.
 * 
 * @param text
 *            컴퍼넌트에 표시할 문자열
 */
Component.prototype.setText = function setText(text) {
	this.element.innerHTML = text;
};

/**
 * 컴퍼넌트 내의 글을 리턴합니다.
 * 
 * @returns
 */
Component.prototype.getText = function getText() {
	return this.element.innerHTML;
};

/**
 * 컴퍼넌트 내 글꼴 사이즈를 설정합니다.
 * 
 * @param size
 *            글 크기
 */
Component.prototype.setFontSize = function setFontSize(size) {
	this.element.style.fontSize = size;
};

/**
 * 컴퍼넌트 내 글꼴을 설정합니다.
 * 
 * @param font
 */
Component.prototype.setFontFamily = function setFontFamilyfont() {
	this.element.style.fontFamily = font;
};

/**
 * 컴퍼넌트가 가리키는 HTML 객체의 속성을 설정합니다.
 * 
 * @param key
 *            속성 키
 * @param value
 *            속성 키의 값
 */
Component.prototype.setAttribute = function setAttribute(key, value) {
	this.element.setAttribute(key, value);
};

/**
 * 컴퍼넌트가 가리키는 HTML 객체의 속성을 리턴합니다.
 * 
 * @param key
 *            HTML 객체의 속성 키
 * @returns 키에 해당하는 속성값
 */
Component.prototype.getAttrubute = function getAttrubute(key) {
	return this.element.getAttribute(key);
};

Component.prototype.setDraggable = function setDraggable(flag, bound) {
	this.draggable = flag;
	if (this.draggable) {
		this.onDragStart = function() {
			this.dragging = true;
			this.isAnimating = false;

			// event.currentTarget.style.position = "absolute";
			// event.currentTarget.style.webkitTransition="all 0.1s linear";

			this.dragTrasitionEndRef = Delegate.create(this, function() {
				this.isAnimating = false;
			});
			// this.addEventListener("webkitTransitionEnd",this.dragTrasitionEndRef);

			window.addEventListener(this.moveEvent, this.dragMoveRef);
			// event.stopPropagation();

			if (this.uimanager.isTouch) {
				var point = window.webkitConvertPointFromNodeToPage(
						this.element, new WebKitPoint(0, 0));
				this.startPosition = {
					x : event.touches[0].pageX - point.x,
					y : event.touches[0].pageY - point.y
				};
			} else {
				this.startPosition = {
					x : event.offsetX,
					y : event.offsetY
				};
			}
		};

		this.onDragMove = function() {
			if (this.dragging) {
				var x = "";
				var y = "";

				if (this.uimanager.isTouch) {
					x = event.touches[0].pageX - this.startPosition.x;
					y = event.touches[0].pageY - this.startPosition.y;
					this.lastTouchX = event.touches[0].pageX;
					this.lastTouchY = event.touches[0].pageY;
				} else {
					x = event.pageX - this.startPosition.x;
					y = event.pageY - this.startPosition.y;

					this.lastTramsformX = x;
					this.lastTramsformY = y;
				}

				this.isAnimating = true;
				this.element.style.webkitTransform = "translate(" + x + "px,"
						+ y + "px)";

				event.stopPropagation();
			} else {
				log.log("skip!!!!!!!!!!!!!!!!!");
			}
		};

		this.onDragEnd = function() {
			this.dragging = false;
			window.removeEventListener(this.moveEvent, this.dragMoveRef);

			this.setStyle("left", this.lastTramsformX);
			this.setStyle("top", this.lastTramsformY);

			this.isAnimating = false;

			event.stopPropagation();
		};

		this.dragMoveRef = Delegate.create(this, this.onDragMove);
		this.dragStartRef = Delegate.create(this, this.onDragStart);
		this.dragEndRef = Delegate.create(this, this.onDragEnd);

		this.addEventListener(this.startEvent, this.dragStartRef);
		this.addEventListener(this.endEvent, this.dragEndRef);
	} else {
		this.removeEventListener(this.startEvent, this.dragStartRef);
		this.removeEventListener(this.endEvent, this.dragEndRef);
		window.removeEventListener(this.moveEvent, this.dragMoveRef);
		this.dragging = false;
	}
};

/**
 * 컴퍼넌트의 margin을 포함함 height를 리턴합니다. API 후보
 * 
 * @param key
 *            HTML 객체의 속성 키
 * @returns 키에 해당하는 속성값
 */
Component.prototype.getOuterHeight = function getOuterHeight() {
	var marginTop = this.getStyle("margin-top").split("px")[0];
	var marginBottom = this.getStyle("margin-bottom").split("px")[0];
	return this.getOffsetHeight() + Number(marginTop) + Number(marginBottom);
};

Component.prototype.setFormatter = function setFormatter(formatter) {
	this.formatter = formatter;
};

/**
 * 버튼 관련 UI 컴퍼넌트들의 베이스 클래스로서 여러 클래스에 공통적인 메소드와 속성을 가지고 있습니다.
 * 
 * @base Component
 * @constructor
 */
var BaseButtonComponent = function BaseButtonComponent(id, dataMapId) {
	if (id == undefined)
		return;
	this.uimanager = uimanager;
	this.id = id;
	this.dataMapId = dataMapId;
	this.componentName = arguments.callee.caller.name;

	this.enabled = true;
	this.draggable = false;
	this.data = null;

	this.onClickHandler = null;
	this.onLongClickHandler = null;

	this.element = document.getElementById(id);
	if (this.element == null) {
		this.element = this.createElement();
	}
	this.element.component = this;
	if (this.element.parentNode != null) {
		this.parent = this.element.parentNode.component;
	}

	this.mouseMovedFlag = false;
	this.longClickFlag == false;

	this.normalStyleClass = this.element.className;
	this.focusStyleClass = this.normalStyleClass + " " + this.normalStyleClass
			+ "Active";
	this.disableStyleClass = this.normalStyleClass + " "
			+ this.normalStyleClass + "Disable";

	this._ontouchend = function(e) {
		if (e.currentTarget.component.longClickFlag) {
			e.currentTarget.component.longClickFlag = false;
			return;
		}

		if (e.currentTarget.component.mouseMovedFlag) {
			e.currentTarget.component.mouseMovedFlag = false;
			return;
		}

		clearTimeout(e.currentTarget.tid);
		e.currentTarget.className = e.currentTarget.component.normalStyleClass;
		if (e.currentTarget.component.onClickHandler != null) {
			e.currentTarget.component.onClickHandler(e);
		}
	};

	this._ontouchstart = function(e) {
		e.preventDefault();
		e.currentTarget.component.mouseMovedFlag = false;

		if (e.currentTarget.component.onLongClickHandler != null)
			e.currentTarget.tid = setTimeout(function(obj, e) {
				return function() {
					obj._onLongClickHandler(e);
				};
			}.apply(this.component, [ this.component, {
				target : e.target,
				currentTarget : e.currentTarget
			} ]), BaseButtonComponent.LONG_CLICK_INTERVAL);

		var pageX = uimanager.isTouch ? e.changedTouches[0].pageX : e.pageX;
		var pageY = uimanager.isTouch ? e.changedTouches[0].pageY : e.pageY;

		e.currentTarget.prevX = pageX;
		e.currentTarget.prevY = pageY;
		e.currentTarget.className = e.currentTarget.component.focusStyleClass;
	};
	this._ontouchmove = function(e) {
		e.preventDefault();

		var pageX = uimanager.isTouch ? e.changedTouches[0].pageX : e.pageX;
		var pageY = uimanager.isTouch ? e.changedTouches[0].pageY : e.pageY;

		if (pageX - e.currentTarget.prevX > 4
				|| pageX - e.currentTarget.prevX < -4
				|| pageY - e.currentTarget.prevY > 4
				|| pageY - e.currentTarget.prevY < -4) {
			e.currentTarget.className = e.currentTarget.component.normalStyleClass;
			e.currentTarget.component.mouseMovedFlag = true;
			clearTimeout(e.currentTarget.tid);
		}
	};

	this._onLongClickHandler = function(e) {
		clearTimeout(e.currentTarget.tid);
		e.currentTarget.className = e.currentTarget.component.normalStyleClass;
		e.currentTarget.component.longClickFlag = true;

		if (e.currentTarget.component.onLongClickHandler != null) {
			e.currentTarget.component.onLongClickHandler(e);
		}
	};

	this.setEventType();

	this.element.addEventListener(this.startEvent, this._ontouchstart, false);
	this.element.addEventListener(this.moveEvent, this._ontouchmove, false);
	this.element.addEventListener(this.endEvent, this._ontouchend, false);

	this.initialize();

}.inherits(Component);

BaseButtonComponent.LONG_CLICK_INTERVAL = 600;

/**
 * 롱클릭 이벤트 핸들러를 등록합니다.
 * 
 * @param handler
 *            이벤트 핸들러 함수
 */
BaseButtonComponent.prototype.setOnLongClickHandler = function setOnLongClickHandler(
		handler) {
	this.onLongClickHandler = handler;
};

/**
 * 클릭 이벤트 핸들러를 등록합니다.
 * 
 * @param handler
 *            이벤트 핸들러 함수
 */
BaseButtonComponent.prototype.setOnClickHandler = function setOnClickHandler(
		handler) {
	this.onClickHandler = handler;
};

/**
 * 컴퍼넌트에 스타일 클래스 이름을 지정합니다.
 * 
 * @param className
 *            스타일 클래스 이름
 */
BaseButtonComponent.prototype.setStyleClass = function(className) {
	this.element.className = className;
	this.normalStyleClass = this.element.className;
	this.focusStyleClass = this.normalStyleClass + " " + this.normalStyleClass
			+ "Active";
	this.disableStyleClass = this.normalStyleClass + " "
			+ this.normalStyleClass + "Disable";
};

/**
 * 컴퍼넌트를 활성화시킬 지 설정합니다.
 * 
 * @param value
 *            Boolean(True/False) 값
 */
BaseButtonComponent.prototype.setEnabled = function(value) {
	if (this.enabled == value)
		return;
	if (value) {
		this.element.className = this.normalStyleClass;
		this.element.addEventListener(this.START_EVENT, this._ontouchstart,
				false);
		this.element
				.addEventListener(this.MOVE_EVENT, this._ontouchmove, false);
		this.element.addEventListener(this.END_EVENT, this._ontouchend, false);
	} else {
		this.element.className = this.disableStyleClass;
		this.element.removeEventListener(this.START_EVENT, this._ontouchstart,
				false);
		this.element.removeEventListener(this.MOVE_EVENT, this._ontouchmove,
				false);
		this.element.removeEventListener(this.END_EVENT, this._ontouchend,
				false);
	}
	this.enabled = value;
};
/**
 * 컴퍼넌트가 활성화 여부 값을 리턴합니다.
 * 
 * @returns {Boolean}
 */
BaseButtonComponent.prototype.getEnabled = function() {
	return this.enabled;
};

/**
 * 베이스 클래스로서 Panel, Table, TableRow, TableCell 클래스에 공통된 메소드와 속성을 제공합니다.
 * 
 * @base Component
 * @constructor
 */
var LayoutComponent = function LayoutComponent(id, dataMapId) {
	if (id == undefined)
		return;
	this.uimanager = uimanager;

	this.componentName = arguments.callee.caller.name;
	this.id = id;
	this.dataMapId = dataMapId;

	this.draggable = false;
	this.childComponents = [];

	this.element = document.getElementById(id);
	if (this.element == null) {
		this.element = this.createElement();
	}
	this.element.component = this;
	if (this.element.parentNode != null) {
		this.parent = this.element.parentNode.component;
	}

	this.setEventType();
	this.initialize();

}.inherits(Component);
/**
 * 컴퍼넌트내에 새로운 자식 컴퍼넌트를 추가합니다.
 * 
 * @param component
 *            자식 컴퍼넌트
 */
LayoutComponent.prototype.addComponent = function addComponent(component) {
	this.childComponents.push(component);
	if (component.element.parentNode == null) {
		this.element.appendChild(component.element);
	}

	var event = document.createEvent("Event");
	event.initEvent("added", true, false);
	event.component = this;
	component.dispatchEvent(event);
};

/**
 * 인자로 주어진 인덱스의 자식 컴퍼넌트를 리턴합니다.
 * 
 * @param pos
 *            인덱스
 * @returns 인덱스에 위치한 자식 컴퍼넌트
 */
LayoutComponent.prototype.getComponentAt = function getComponent(pos) {
	return this.childComponents[pos];
};

/**
 * 인자로 주어진 자식 컴퍼넌트를 삭제합니다.
 * 
 * @param component
 *            컴퍼넌트
 */
LayoutComponent.prototype.removeComponent = function removeComponent(component) {
	var pos = -1;
	for ( var int = 0; int < this.childComponents.length; int++) {
		if (component == this.childComponents[int]) {
			pos = int;
			break;
		}
	}

	if (pos > -1) {
		this.removeComponentAt(pos);
	}
};

/**
 * 인자로 주어진 인덱스에 위치한 자식 컴퍼넌트를 삭제합니다.
 * 
 * @param pos
 *            인덱스
 */
LayoutComponent.prototype.removeComponentAt = function removeComponentAt(pos) {
	this.childComponents[pos].remove();
	this.childComponents.splice(pos, 1);
};

/*******************************************************************************
 * Layout
 ******************************************************************************/

/**
 * Layout 은 다른 컴퍼넌트들을 포함하는 컴퍼넌트입니다. Layout 은 구조를 잡기위한 컴퍼넌트로서 UI 상에 보여지지는 않습니다.
 * 
 * @constructor
 */
function Layout() {
	this.components = new Array();
}
/**
 * 포커스되어 있는 컴퍼넌트의 인스턴스를 리턴합니다.
 * 
 * @returns Component
 */
Layout.prototype.getFocus = function() {
	for ( var i = 0; i < this.components.length; i++) {
		if (this.components[i].focused) {
			return this.components[i];
		}
	}
	return null;
};
/**
 * 자식 컴퍼넌트를 추가합니다.
 * 
 * @param Component
 * @returns void
 */
Layout.prototype.addComponent = function(component) {
	this.components.push(component);
};

/**
 * 자식 컴퍼넌트를 제거합니다.
 * 
 * @param Component
 * @returns void
 */
Layout.prototype.removeComponent = function(component) {
	this.components.remove(component);
};

/**
 * 컴퍼넌트에 데이터를 지정합니다.
 * 
 * @param dataSet
 * @returns void
 */
Layout.prototype.setData = function(dataSet) {
	var component = null;
	var value = null;
	for ( var i = 0; i < this.components.length; i++) {
		component = this.components[i];
		if (component.dataMapId == "list") {
			if (dataSet.list != "undefined" && dataSet.list != null
					&& dataSet.list.size() > 0)
				component.setData(dataSet
						.getList(dataSet.list.getKeyByIndex(0)));
			continue;
		}

		value = dataSet.getList(component.dataMapId);
		if (value != null) {
			component.setData(value);
			continue;
		}

		if (component.dataMapId != null) {
			value = dataSet.get(component.dataMapId);
			if (value != null) {
				component.setData(value);
			} else {
				component.setData("");
			}
		}
	}
};

// ================================================================================================================//
// ================================================== Panel
// =====================================================//
// ================================================================================================================//
/**
 * 인자로 주어진 ID를 가진 Panel을 생성합니다. Panel 은 다른 컴퍼넌트를 포함할 수 있어서 여러개의 자식 컴퍼넌트를 가질 수
 * 있습니다.
 * 
 * @base LayoutComponent
 * @constructor
 */
var Panel = function Panel(id, dataMapId) {
	if (id == undefined)
		return;

	this.parent(id, dataMapId);
}.inherits(LayoutComponent);

Panel.prototype.setData = function setData(data) {
	if (data instanceof DataSet) {
		var dataObj = data.map._data;
		for ( var key in dataObj) {
			var nodes = document.getElementsByName(key);

			for ( var i = 0; i < nodes.length; i++) {
				el = nodes[i];
				if (this.isChild(el)) {
					if (el.component != undefined) {
						el.component.setData(dataObj[key]);
					}
				}
			}
		}
	} else
		this.element.innerHTML = data;
};

Panel.prototype.isChild = function isChild(el) {
	do {
		if (el.parentNode == this.element) {
			return true;
		}
		el = el.parentNode;
	} while (el != null);

	return false;
};

// ================================================================================================================//
// ================================================= TextView
// =====================================================//
// ================================================================================================================//

/**
 * 디폴트 생성자로서 TextView 컴퍼넌트를 생성합니다. TextView 는 짧은 문자열을 출력하는 컴퍼넌트입니다.
 * 
 * @base Component
 * @param id,
 *            dataMapId
 * @constructor
 */
var TextView = function TextView(id, dataMapId) {
	if (id == undefined)
		return;

	this.parent(id, dataMapId);
	this.text = "";
}.inherits(Component);

/**
 * 문자열의 정렬방식을 지정합니다.
 * 
 * @return
 */
TextView.prototype.setAlign = function(align) {
	var alignArr = align.split("|");
	for ( var i = 0; i < alignArr.length; i++) {

		if (alignArr[i] == "center" || alignArr[i] == "left"
				|| alignArr[i] == "right") {
			this.element.style.textAlign = alignArr[i];
		} else if (alignArr[i] == "middle" || alignArr[i] == "top"
				|| alignArr[i] == "bottom") {
			if (alignArr[i] == "middle") {
				this.element.style.lineHeight = this.element.style.height;
			}
		}
	}
};

// ================================================================================================================//
// ================================================= Label
// =====================================================//
// ================================================================================================================//
/**
 * Label은 문자열을 출력하기 위한 component 입니다.
 * 
 * @param id
 *            컴퍼넌트 id
 * @param dataMapId
 * @constructor
 * @base Component
 */
var Label = function Label(id, dataMapId) {
	if (id == undefined)
		return;
	this.parent(id, dataMapId);

	this.text = "";

	this.mouseMovedFlag = false;
	this.longClickFlag == false;

	this.normalStyleClass = this.element.className;
	this.focusStyleClass = this.normalStyleClass + " " + this.normalStyleClass
			+ "Active";
	this.disableStyleClass = this.normalStyleClass + " "
			+ this.normalStyleClass + "Disable";

	this.START_EVENT = uimanager.isTouch ? 'touchstart' : 'mousedown';
	this.MOVE_EVENT = uimanager.isTouch ? 'touchmove' : 'mousemove';
	this.END_EVENT = uimanager.isTouch ? 'touchend' : 'mouseup';

	this._ontouchend = function(e) {
		if (e.currentTarget.component.longClickFlag) {
			e.currentTarget.component.longClickFlag = false;
			return;
		}

		if (e.currentTarget.component.mouseMovedFlag) {
			e.currentTarget.component.mouseMovedFlag = false;
			return;
		}

		if (e.currentTarget.component.onClickHandler != null) {
			e.currentTarget.component.onClickHandler(e);
		}
	};

	this._ontouchstart = function(e) {
		e.preventDefault();
		e.currentTarget.component.mouseMovedFlag = false;

		var pageX = uimanager.isTouch ? e.changedTouches[0].pageX : e.pageX;
		var pageY = uimanager.isTouch ? e.changedTouches[0].pageY : e.pageY;

		e.currentTarget.prevX = pageX;
		e.currentTarget.prevY = pageY;
	};
	this._ontouchmove = function(e) {
		e.preventDefault();

		var pageX = uimanager.isTouch ? e.changedTouches[0].pageX : e.pageX;
		var pageY = uimanager.isTouch ? e.changedTouches[0].pageY : e.pageY;

		if (pageX - e.currentTarget.prevX > 4
				|| pageX - e.currentTarget.prevX < -4
				|| pageY - e.currentTarget.prevY > 4
				|| pageY - e.currentTarget.prevY < -4) {
			e.currentTarget.component.mouseMovedFlag = true;
			clearTimeout(e.currentTarget.tid);
		}
	};

	this.element.addEventListener(this.START_EVENT, this._ontouchstart, false);
	this.element.addEventListener(this.MOVE_EVENT, this._ontouchmove, false);
	this.element.addEventListener(this.END_EVENT, this._ontouchend, false);

}.inherits(Component);
/**
 * 문자열의 정렬방식을 지정합니다. (left:왼쪽 정렬, right: 오른쪽 정렬, center: 가운데 정렬 등등)
 * 
 */
Label.prototype.setAlign = function(align) {
	var alignArr = align.split("|");
	for ( var i = 0; i < alignArr.length; i++) {

		if (alignArr[i] == "center" || alignArr[i] == "left"
				|| alignArr[i] == "right") {
			this.element.style.textAlign = alignArr[i];
		} else if (alignArr[i] == "middle" || alignArr[i] == "top"
				|| alignArr[i] == "bottom") {
			if (alignArr[i] == "middle") {
				this.element.style.lineHeight = this.element.style.height;
			}
		}
	}
};

// ================================================================================================================//
// ================================================= Label
// =====================================================//
// ================================================================================================================//

/**
 * 인자로 주어진 컴퍼넌트 ID와 dataMap ID를 가진 TextFiled 컴퍼넌트를 생성합니다.
 * 
 * @param id
 *            컴퍼넌트 id
 * @param dataMapId
 * @constructor
 * @extends Component
 */
var TextField = function TextField(id, dataMapId) {
	if (id == undefined)
		return;

	this.parent(id, dataMapId);

	this.text = "";
}.inherits(Component);

/**
 * 문자열의 정렬 방식을 지정합니다. (왼쪽 정렬, 오른쪽 정렬, 가운데 정렬 등등)
 */
TextField.prototype.setAlign = function(align) {
	var alignArr = align.split("|");
	for ( var i = 0; i < alignArr.length; i++) {

		if (alignArr[i] == "center" || alignArr[i] == "left"
				|| alignArr[i] == "right") {
			this.element.style.textAlign = alignArr[i];
		} else if (alignArr[i] == "middle" || alignArr[i] == "top"
				|| alignArr[i] == "bottom") {
			if (alignArr[i] == "middle") {
				this.element.style.lineHeight = this.element.style.height;
			}
		}
	}
};

// ================================================================================================================//
// ================================================= TextInput
// ====================================================//
// ================================================================================================================//
/**
 * Textinput 은 문자열 값을 입력받기 위한 컴퍼넌트입니다. HTML 에서 input 컴퍼넌트와 유사한 기능을 합니다.
 * 
 * @param id
 *            컴퍼넌트 id
 * @param dataMapId
 * @constructor
 * @base Component
 */
var TextInput = function TextInput(id, dataMapId) {
	if (id == undefined)
		return;

	this.parent(id, dataMapId);

	this.defaultText = "";
	this.isDefualt = true;

	this.focused = false;

	this._onfocus = function(e) {
		this.component.focused = true;
		if (this.component.onfocus != null)
			this.component.onfocus(e);
	};
	this.onfocus = null;
	this.element.onfocus = this._onfocus;
	this._onblur = function(e) {
		this.component.focused = false;
		if (this.component.onblur != null)
			this.component.onblur(e);
	};
	this.onblur = null;
	this.element.onblur = this._onblur;
}.inherits(Component);

/**
 * TextInput 컴퍼넌트의 DOM Element를 동적으로 생성합니다.
 */
TextInput.prototype.createElement = function createElement() {
	var el = document.createElement("INPUT");
	el.setAttribute("type", "text");
	return el;
};

/**
 * 문자열의 정렬 방식을 지정합니다.
 * 
 * @param align
 *            정렬방식 (left, right, center 등등)
 */
TextInput.prototype.setAlign = function setAlign(align) {
	var alignArr = align.split("|");
	for ( var i = 0; i < alignArr.length; i++) {

		if (alignArr[i] == "center" || alignArr[i] == "left"
				|| alignArr[i] == "right") {
			this.element.style.textAlign = alignArr[i];
		} else if (alignArr[i] == "middle" || alignArr[i] == "top"
				|| alignArr[i] == "bottom") {
			if (alignArr[i] == "middle") {
				this.element.style.lineHeight = this.element.style.height;
			}
		}
	}
};

/**
 * TextInput 컴퍼넌트의 input type 을 지정합니다.
 * 
 * @param type
 *            타입(input, password 등등)
 */
TextInput.prototype.setType = function setType(type) {
	this.element.type = type;
};
/**
 * TextInput 문자열이 있는지 확인합니다.
 */
TextInput.prototype.checkEmpty = function checkEmpty() {
	if (this.defaultText != "") {
		if (this.element.value == "") {
			this.element.text = this.defaultText;
			this.isDefualt = true;
		}
	}
};

/**
 * 디폴트 문자열을 지정합니다.
 * 
 * @param 디폴트로
 *            보여질 문자열
 */
TextInput.prototype.setDefaultText = function setDefaultText(str) {
	this.defaultText = str;
	if (this.element.value == "")
		this.element.value = str;
};

/**
 * TextInput 컴퍼넌트에 문자열 값을 입력합니다.
 * 
 * @param value
 *            문자열
 */
TextInput.prototype.setData = function setData(value) {
	this.element.value = value;
};

/**
 * TextInput 컴퍼넌트에 쓰여진 문자열을 리턴합니다.
 * 
 * @return TextInput에 쓰여진 문자열
 */
TextInput.prototype.getData = function getData() {
	return this.element.value;
};
/**
 * setData 함수와 동일
 * 
 * @param value
 */
TextInput.prototype.setText = function setText(value) {
	this.element.value = value;
};

/**
 * 컴퍼넌트의 문자열 값을 리턴합니다.
 */
TextInput.prototype.getText = function getText() {
	return this.element.value;
};
/**
 * 문자열을 입력 후 키보드가 눌려진 후 띄어졌을 때 발생하는 이벤트의 핸들러를 등록합니다.
 * 
 * @param handler
 *            핸들러 함수
 */
TextInput.prototype.setOnKeyUpHandler = function setOnKeyUpHandler(handler) {
	this.element.onkeyup = handler;
};
/**
 * TextInput 컴퍼넌트가 포커스 되어있는 상태에서 키가 눌려졌을때 발생하는 이벤트의 핸들러를 등록합니다.
 * 
 * @param handler
 *            핸들러 함수
 */
TextInput.prototype.setOnKeyPressHandler = function setOnKeyPressHandler(
		handler) {
	this.element.onkeypress = handler;
};

/**
 * TextInput 컴퍼넌트가 포커스되어 질 때 발생하는 이벤트를 등록합니다.
 * 
 * @param handler
 *            핸들러 함수
 */
TextInput.prototype.setOnFocusHandler = function setOnFocusHandler(handler) {
	this.onfocus = handler;
	this.element.onfocus = this._onfocus;
};
/**
 * TextInput 컴퍼넌트에서 포커스를 잃어 버렸을 때 발생하는 이벤트를 등록합니다.
 * 
 * @param handler
 *            핸들러 함수
 */
TextInput.prototype.setOnBlurHandler = function setOnBlurHandler(handler) {
	this.onblur = handler;
	this.element.onblur = this._onblur;
};
// ================================================================================================================//
// ================================================= TextArea
// ====================================================//
// ================================================================================================================//
/**
 * TextArea 는 긴 문단의 문자열의 입력을 받을 때 사용되는 컴퍼넌트입니다. 디폴트 생성자로서 TextArea 컴퍼넌트를 생성합니다.
 * 
 * @constructor
 * @base Component
 */
var TextArea = function TextArea(id, dataMapId) {
	if (id == undefined)
		return;

	this.parent(id, dataMapId);
	/**
	 * 문자열 값이 지정되지 않았을 시 보여지는 문자열
	 * 
	 * @member
	 */
	this.defaultText = "";

	this._onfocus = function(e) {
		this.component.focused = true;
		if (this.component.onfocus != null)
			this.component.onfocus(e);
	};
	this.onfocus = null;
	this.element.onfocus = this._onfocus;
	this._onblur = function(e) {
		this.component.focused = false;
		if (this.component.onblur != null)
			this.component.onblur(e);
	};
	this.onblur = null;
	this.element.onblur = this._onblur;
}.inherits(Component);

/**
 * TextInput 컴퍼넌트의 DOM Element를 동적으로 생성합니다.
 */
TextArea.prototype.createElement = function createElement() {
	var el = document.createElement("TEXTAREA");
	return el;
};

/**
 * 컴퍼넌트의 type을 지정합니다.
 * 
 * @param type
 *            타입 (input / password)
 */
TextArea.prototype.setType = function setType(type) {
	this.element.type = type;
};

/**
 * 컴퍼넌트가 문자열 값이 있는지 점검합니다. (비어있을 시 true, 값이 있을 시 false)
 */
TextArea.prototype.checkEmpty = function checkEmpty() {
	if (this.defaultText != "") {
		if (this.element.value == "") {
			this.element.text = this.defaultText;
			this.isDefualt = true;
		}
	}
};

/**
 * 문자열 값이 지정되지 않을 시 보여지는 디폴트 문자열을 지정합니다.
 * 
 * @param str
 *            디폴트 문자열
 */
TextArea.prototype.setDefaultText = function setDefaultText(str) {
	this.defaultText = str;
	if (this.element.value == "")
		this.element.value = str;
};

/**
 * TextArea 컴퍼넌트에 문자열 값을 입력합니다.
 * 
 * @param value
 *            문자열
 */
TextArea.prototype.setData = function setData(value) {
	this.element.value = value;
};

/**
 * TextArea 컴퍼넌트의 문자열을 리턴합니다.
 * 
 * @return 문자열
 */
TextArea.prototype.getData = function getData() {
	return this.element.value;
};

/**
 * setData 함수와 동일합니다.
 * 
 * @param textStr
 */
TextArea.prototype.setText = function setText(textStr) {
	this.element.value = textStr;
};
/**
 * getData 함수와 동일합니다.
 * 
 * @returns
 */
TextArea.prototype.getText = function getText() {
	return this.element.value;
};
/**
 * TextArea 컴퍼넌트에서 키보드가 눌려졌을 때 발생하는 이벤트의 핸들러를 등록합니다.
 * 
 * @param handler
 *            핸들러 함수
 */
TextArea.prototype.setOnKeyPressHandler = function setOnKeyPressHandler(handler) {
	this.element.onkeypress = handler;
};

/**
 * TextArea 컴퍼넌트에서 키보드가 눌려진 후 띄어졌을 때 발생하는 이벤트의 핸들러를 등록합니다.
 * 
 * @param handler
 *            핸들러 함수
 */
TextArea.prototype.setOnKeyUpHandler = function setOnKeyUpHandler(handler) {
	this.element.onkeyup = handler;
};

/**
 * TextArea 컴퍼넌트가 포커스되어 질 때 발생하는 이벤트를 등록합니다.
 * 
 * @param handler
 *            핸들러 함수
 */
TextArea.prototype.setOnFocusHandler = function setOnFocusHandler(handler) {
	this.onfocus = handler;
	this.element.onfocus = this._onfocus;
};

/**
 * TextArea 컴퍼넌트에서 포커스를 잃어 버렸을 때 발생하는 이벤트 핸들러를 등록합니다.
 * 
 * @param handler
 *            핸들러 함수
 */
TextArea.prototype.setOnBlurHandler = function setOnBlurHandler(handler) {
	this.onblur = handler;
	this.element.onblur = this._onblur;
};

TextArea.prototype.setAutoResize = function setAutoResize(flag) {
	if (flag) {
		if (this.shadowClone == undefined)
			this.shadowClone = document.createElement("textarea");
		this.shadowClone.className = this.element.className;
		this.shadowClone.style.width = this.getStyle("width");
		this.shadowClone.style.height = this.getStyle("height");
		this.shadowClone.style.lineHeight = this.getStyle("lineHeight");
		this.shadowClone.style.fontFamily = this.getStyle("fontFamily");
		this.shadowClone.style.fontSize = this.getStyle("fontSize");

		this.shadowClone.style.position = "absolute";
		this.shadowClone.style.left = "-10000px";
		this.shadowClone.style.top = "-10000px";
		this.shadowClone.style.overFlow = "hidden";

		document.body.appendChild(this.shadowClone);

		this.addEventListener("input", this.oninput);

	} else {
		this.removeEventListener("input", this.oninput);
		if (this.shadowClone != undefined)
			document.body.removeChild(this.shadowClone);
		delete this.shadowClone;
	}
	this.autoResize = flag;

};

TextArea.prototype.oninput = function oninput(event) {
	this.shadowClone.value = this.getText();

	if (this.shadowClone.scrollHeight > this.element.clientHeight) {
		this.element.rows += 1;
	} else if (this.shadowClone.scrollHeight < this.element.clientHeight) {
		this.element.rows -= 1;
	}

};
// ================================================================================================================//
// ================================================== Dialog
// ======================================================//
// ================================================================================================================//
var DIALOG_TIMER = 50;
var FADE_SPEED = 20;
// var WRAPPER = 'content';

/**
 * Dialog 컴퍼넌트는 팝업창을 가리키는 컴퍼넌트입니다.
 * 
 * @constructor
 */
function Dialog() {
}

/**
 * Dialog를 보여줍니다.
 * 
 * @param title
 *            Dialog의 제목
 * @param message
 *            Dialog의 메세지
 * @param type
 *            Dialog의 타입
 * @param autohide
 */
Dialog.showDialog = function(title, message, type, autohide) {
	if (!type) {
		type = 'alert';
	}

	var dialog;
	var dialogheader;
	var dialogclose;
	var dialogtitle;
	var dialogcontent;
	var dialogmask;

	dialog = document.getElementById('dialog');
	if (dialog != null) {
		dialog.parentNode.removeChild(dialog);
		document.getElementById('dialog-mask').parentNode.removeChild(document
				.getElementById('dialog-mask'));
	}
	dialog = document.createElement('div');
	dialog.id = 'dialog';
	dialog.className = "Dialog";
	dialogheader = document.createElement('div');
	dialogheader.id = 'dialog-header';
	dialogtitle = document.createElement('div');
	dialogtitle.id = 'dialog-title';
	dialogtitle.className = "DialogElement" + " " + "Title";
	dialogclose = document.createElement('div');
	dialogclose.id = 'dialog-close';
	dialogclose.className = "DialogElement" + " " + "Close";

	dialogcontent = document.createElement('div');
	dialogcontent.id = 'dialog-content';
	dialogmask = document.createElement('div');
	dialogmask.id = 'dialog-mask';
	dialogmask.className = "DialogElement" + " " + "Mask";
	document.body.appendChild(dialogmask);
	document.body.appendChild(dialog);
	dialog.appendChild(dialogheader);
	dialogheader.appendChild(dialogtitle);
	dialogheader.appendChild(dialogclose);
	dialog.appendChild(dialogcontent);
	;
	dialog.style.opacity = .00;
	dialog.style.filter = 'alpha(opacity=0)';
	dialog.alpha = 0;
	dialog.style.zIndex = 200;

	dialogheader.className = "DialogElement" + " " + type + "Header";
	dialogtitle.innerHTML = title;
	dialogcontent.className = "DialogElement" + " " + type;
	dialogcontent.innerHTML = message;
	var width = Dialog.pageWidth();
	var height = Dialog.pageHeight();
	// var left = Dialog.leftPosition();
	// var top = Dialog.topPosition();
	var dialogwidth = dialog.offsetWidth;
	var dialogheight = dialog.offsetHeight;

	var topposition = (height / 2) - (dialogheight / 2);
	var leftposition = (width / 2) - (dialogwidth / 2);
	dialog.style.top = topposition + "px";
	dialog.style.left = leftposition + "px";

	if (dialog.SHOW_TIMER != null)
		window.clearTimeout(dialog.SHOW_TIMER);
	dialog.DIALOG_TIMER = setInterval("Dialog.fadeDialog(1)", DIALOG_TIMER);
	if (autohide) {
		dialogclose.style.visibility = "hidden";
		dialog.SHOW_TIMER = window.setTimeout("Dialog.closeDialog()",
				(autohide * 1000));
	} else {
		dialogclose.style.visibility = "visible";
	}
	window.addEventListener("resize", Dialog.onWindowResize, false);
};
/**
 * 컴퍼넌트 윈도우의 사이즈가 변경될 시 발생하는 이벤트 핸들러를 등록합니다.
 */
Dialog.onWindowResize = function(event) {
	var dialog = document.getElementById('dialog');
	if (dialog != null) {
		var width = Dialog.pageWidth();
		var height = Dialog.pageHeight();
		// var left = Dialog.leftPosition();
		// var top = Dialog.topPosition();
		var dialogwidth = dialog.offsetWidth;
		var dialogheight = dialog.offsetHeight;

		var topposition = (height / 2) - (dialogheight / 2);
		var leftposition = (width / 2) - (dialogwidth / 2);
		dialog.style.top = topposition + "px";
		dialog.style.left = leftposition + "px";
	}
};

/**
 * Dialog를 닫습니다.
 */
Dialog.closeDialog = function hideDialog() {
	var dialog = document.getElementById('dialog');
	clearInterval(dialog.DIALOG_TIMER);
	dialog.DIALOG_TIMER = setInterval("Dialog.fadeDialog(0)", DIALOG_TIMER);
};
/**
 * 창이 닫힐 때 Fade out 효과를 줍니다.
 * 
 * @param flag
 */
Dialog.fadeDialog = function(flag) {
	if (flag == null) {
		flag = 1;
	}
	var dialog = document.getElementById("dialog");
	var value;
	if (flag == 1) {
		value = dialog.alpha + FADE_SPEED;
	} else {
		value = dialog.alpha - FADE_SPEED;
	}

	dialog.alpha = value;
	dialog.style.opacity = (value / 100);
	dialog.style.filter = 'alpha(opacity=' + value + ')';
	if (value >= 98) {
		clearInterval(dialog.DIALOG_TIMER);
		dialog.DIALOG_TIMER = null;
		if (document.getElementById('dialog-close').style.visibility == "visible") {
			document.getElementById('dialog-close').onclick = Dialog.closeDialog;
			debuger.log(document.getElementById('dialog-close').onclick);
		}

	} else if (value <= 2) {
		dialog.parentNode.removeChild(dialog);
		document.getElementById('dialog-mask').parentNode.removeChild(document
				.getElementById('dialog-mask'));
		clearInterval(dialog.DIALOG_TIMER);
		dialog.DIALOG_TIMER = null;
		window.removeEventListener("resize", Dialog.onWindowResize, false);

	}
};
/**
 * Dialog의 너비값을 리턴합니다.
 * 
 * @return dialog의 너비값
 */
Dialog.pageWidth = function() {
	// return screen.width;
	return window.innerWidth != null ? window.innerWidth
			: document.documentElement && document.documentElement.clientWidth ? document.documentElement.clientWidth
					: document.body != null ? document.body.clientWidth : null;
};
/**
 * Dialog의 높이값을 리턴합니다.
 * 
 * @return Dialog의 높이값
 */
Dialog.pageHeight = function() {
	// return screen.height;
	return window.innerHeight != null ? window.innerHeight
			: document.documentElement && document.documentElement.clientHeight ? document.documentElement.clientHeight
					: document.body != null ? document.body.clientHeight : null;
};

/**
 * 컴퍼넌트 윈도우의 최상단 좌표를 리턴합니다.
 * 
 * 
 */
Dialog.topPosition = function() {
	return typeof window.pageYOffset != 'undefined' ? window.pageYOffset
			: document.documentElement && document.documentElement.scrollTop ? document.documentElement.scrollTop
					: document.body.scrollTop ? document.body.scrollTop : 0;
};

/**
 * 컴퍼넌트 윈도우의 제일 왼쪽 좌표를 리턴합니다.
 */
Dialog.leftPosition = function() {
	return typeof window.pageXOffset != 'undefined' ? window.pageXOffset
			: document.documentElement && document.documentElement.scrollLeft ? document.documentElement.scrollLeft
					: document.body.scrollLeft ? document.body.scrollLeft : 0;
};

/*
 * ================================================================================================================// //
 * =============================================== Custom Dialog //
 * ===================================================// //
 * ================================================================================================================//
 * /** CustomDialog 는 팝업으로 나타나는 dialog 창으로서 사용자가 customize해서 사용할 수 있습니다. 디폴트
 * 생성자로 CustomDialog 컴퍼넌트를 생성합니다.
 * 
 * @constructor @base Component
 */
function CustomDialog() {
}
/**
 * Custom Dialog를 화면에 보여줍니다.
 * 
 * @param id
 *            custom dialog의 아이디
 */
CustomDialog.showDialog = function(id) {

	var dialog;
	var dialogmask;
	CustomDialog.id = id;
	dialog = document.getElementById(id);
	dialog.style.zIndex = 5000;
	dialogmask = document.createElement('div');
	dialogmask.id = 'dialog-mask';
	dialogmask.className = "CustomDialogMask";
	document.body.appendChild(dialogmask);
	document.body.appendChild(dialog);
	dialog.style.opacity = .00;
	dialog.style.filter = 'alpha(opacity=0)';
	dialog.alpha = 0;
	dialog.style.visibility = "visible";
	dialog.style.position = "absolute";
	var width = CustomDialog.pageWidth();
	var height = CustomDialog.pageHeight();
	// var left = CustomDialog.leftPosition();
	// var top = CustomDialog.topPosition();
	var dialogwidth = dialog.offsetWidth;
	var dialogheight = dialog.offsetHeight;

	var topposition = (height / 2) - (dialogheight / 2);
	var leftposition = (width / 2) - (dialogwidth / 2);
	dialog.style.top = topposition + "px";
	dialog.style.left = leftposition + "px";

	if (dialog.SHOW_TIMER != null)
		window.clearTimeout(dialog.SHOW_TIMER);
	dialog.DIALOG_TIMER = setInterval("CustomDialog.fadeDialog(1)",
			DIALOG_TIMER);
	window.addEventListener("resize", CustomDialog.onWindowResize, false);
};
/**
 * 컴퍼넌트 윈도우의 사이즈가 변경될 시 발생하는 이벤트 핸들러를 등록합니다.
 * 
 * @param event
 *            실행될 이벤트 함수
 */
CustomDialog.onWindowResize = function(event) {
	var dialog = document.getElementById(CustomDialog.id);
	if (dialog != null) {
		var width = CustomDialog.pageWidth();
		var height = CustomDialog.pageHeight();
		// var left = CustomDialog.leftPosition();
		// var top = CustomDialog.topPosition();
		var dialogwidth = dialog.offsetWidth;
		var dialogheight = dialog.offsetHeight;

		var topposition = (height / 2) - (dialogheight / 2);
		var leftposition = (width / 2) - (dialogwidth / 2);
		dialog.style.top = topposition + "px";
		dialog.style.left = leftposition + "px";

	}
};
/**
 * 현재 화면에 보여지는 Dialog를 닫습니다.
 */
CustomDialog.closeDialog = function hideDialog() {
	var dialog = document.getElementById(CustomDialog.id);
	clearInterval(dialog.DIALOG_TIMER);
	dialog.DIALOG_TIMER = setInterval("CustomDialog.fadeDialog(0)",
			DIALOG_TIMER);
};
/**
 * 창이 닫힐 때 Fade out 효과를 줍니다.
 */
CustomDialog.fadeDialog = function(flag) {
	if (flag == null) {
		flag = 1;
	}
	var dialog = document.getElementById(CustomDialog.id);
	var value;
	if (flag == 1) {
		value = dialog.alpha + FADE_SPEED;
	} else {
		value = dialog.alpha - FADE_SPEED;
	}

	dialog.alpha = value;
	dialog.style.opacity = (value / 100);
	dialog.style.filter = 'alpha(opacity=' + value + ')';

	var dialogmask = document.getElementById("dialog-mask");
	dialogmask.style.opacity = (value / 120);

	if (value >= 98) {
		clearInterval(dialog.DIALOG_TIMER);
		dialog.DIALOG_TIMER = null;

	} else if (value <= 2) {
		dialog.style.visibility = "hidden";

		document.getElementById('dialog-mask').parentNode.removeChild(document
				.getElementById('dialog-mask'));
		clearInterval(dialog.DIALOG_TIMER);
		dialog.DIALOG_TIMER = null;
		window
				.removeEventListener("resize", CustomDialog.onWindowResize,
						false);

	}
};
/**
 * 너비값을 리턴합니다.
 */
CustomDialog.pageWidth = function() {
	// return screen.width;
	return window.innerWidth != null ? window.innerWidth
			: document.documentElement && document.documentElement.clientWidth ? document.documentElement.clientWidth
					: document.body != null ? document.body.clientWidth : null;
};
/**
 * 높이값을 리턴합니다.
 */
CustomDialog.pageHeight = function() {
	// return screen.height;
	return window.innerHeight != null ? window.innerHeight
			: document.documentElement && document.documentElement.clientHeight ? document.documentElement.clientHeight
					: document.body != null ? document.body.clientHeight : null;
};
/**
 * 컴퍼넌트 윈도우의 최상단 좌표를 리턴합니다.
 */
CustomDialog.topPosition = function() {
	return typeof window.pageYOffset != 'undefined' ? window.pageYOffset
			: document.documentElement && document.documentElement.scrollTop ? document.documentElement.scrollTop
					: document.body.scrollTop ? document.body.scrollTop : 0;
};
/**
 * 컴퍼넌트 윈도우의 제일 왼쪽 좌표를 리턴합니다.
 */
CustomDialog.leftPosition = function() {
	return typeof window.pageXOffset != 'undefined' ? window.pageXOffset
			: document.documentElement && document.documentElement.scrollLeft ? document.documentElement.scrollLeft
					: document.body.scrollLeft ? document.body.scrollLeft : 0;
};

// ================================================================================================================//
// ================================================== Image
// =======================================================//
// ================================================================================================================//

/**
 * Image는 이미지를 나타내는 컴퍼넌트입니다. 지정된 컴퍼넌트 ID와 dataMap ID를 가지고 image 컴퍼넌트를 생성합니다.
 * 
 * @constructor
 * @base Component
 */
var Image = function Image(id, dataMapId) {
	if (id == undefined)
		return;

	this.parent(id, dataMapId);
}.inherits(BaseButtonComponent);

Image.prototype.setImage = function setImage(url) {
	this.element.style.backgroundImage = "url(" + url + ")";
};

// ================================================================================================================//
// ================================================== Button
// =====================================================//
// ================================================================================================================//

/**
 * Button은 버튼을 나타내는 컴퍼넌트입니다. <br>
 * 디자인을 위해 이미지를 배경으로 버튼을 구성할 수 있습니다.
 * 
 * @constructor
 * @base Component
 */
var Button = function Button(id, dataMapId) {
	if (id == undefined)
		return;

	this.parent(id, dataMapId);
}.inherits(BaseButtonComponent);

/**
 * 버튼 문자열의 정렬방식을 지정합니다.
 * 
 * @param align
 *            "left", "center", "right" 등 정렬방식
 */
Button.prototype.setAlign = function(align) {
	var alignArr = align.split("|");
	for ( var i = 0; i < alignArr.length; i++) {
		if (alignArr[i] == "center" || alignArr[i] == "left"
				|| alignArr[i] == "right") {
			this.element.style.textAlign = alignArr[i];
		} else if (alignArr[i] == "middle" || alignArr[i] == "top"
				|| alignArr[i] == "bottom") {
			if (alignArr[i] == "middle") {
				this.element.style.lineHeight = this.element.style.height;
			}
		}
	}
};

/**
 * 버튼의 문자열을 지정합니다.
 * 
 * @param value
 *            버튼에 나타날 문자열
 */
Button.prototype.setLabel = function(value) {
	this.element.innerHTML = value;
	this.setAlign("center|middle");
};
/**
 * 버튼의 문자열을 리턴합니다.
 * 
 * @return 문자열
 */
Button.prototype.getLabel = function() {
	return this.element.innerHTML = value;

};

/*******************************************************************************
 * ScrollView
 ******************************************************************************/
/**
 * ScrollView는 스크롤바가 생성함으로서 한 페이지 많은 내용을 보여줄 수 있게 하는 컴퍼넌트입니다. ScrollView 컴퍼넌트는
 * iscroll.js 파일을 사용하기 때문에 이 컴퍼넌트를 사용하기 위해서 iscroll.js 파일을 import 해야 합니다.
 * 
 * 지정된 컴퍼넌트 ID와 dataMap ID를 가지고 scrollview 컴퍼넌트를 생성합니다.
 * 
 * @constructor
 * @base Component
 */
var ScrollView = function ScrollView(id, dataMapId) {
	if (id == undefined)
		return;

	this.parent(id, dataMapId);
	this.iscroll = window["scroll" + ScrollView.index];

	ScrollView.index++;

}.inherits(Component);

ScrollView.prototype.setAutoResizeHeight = function setAutoResizeHeight(
		autoResize, elements) {
	this.autoResize = autoResize;
	this.autoResizeElemtns = elements;

	if (this.iscroll == null)
		return;

	if (autoResize) {
		this.onResizeDelegator = Delegate.create(this, this.onResize);
		window
				.addEventListener(
						(device.platformName == "iPhone" && !window.isSimulator) ? "orientationchange"
								: "resize", this.onResizeDelegator, false);
		this.onResize();
	} else {
		this.autoResize = null;
		this.autoResizeElemtns = null;
		window
				.removeEventListener(
						(device.platformName == "iPhone" && !window.isSimulator) ? "orientationchange"
								: "resize", this.onResizeDelegator);
	}
};

ScrollView.prototype.onResize = function onResize(e) {
	var otherHeights = 0;

	for ( var i = 0; i < this.autoResizeElemtns.length; i++) {
		var el = this.autoResizeElemtns[i];
		if (el.getStyle("visibility") == "visible"
				&& el.getStyle("display") != "none") {
			otherHeights += el.getOuterHeight();
		}
	}

	var height = 0;

	// if((device.platform.indexOf("iPad") == -1 ||
	// device.platform.indexOf("iPhone") == -1) && !window.isSimulator ){
	// height = window.innerWidth - otherHeights;
	// }else{
	// height = device.getWindowHeight() - otherHeights;
	// }
	height = device.getWindowHeight() - otherHeights;

	this.setHeight(height + "px");
	this.refresh(); // scrollview 상위에 선언된 iscroll 객체 refresh
};

ScrollView.prototype.refresh = function refresh() {
	this.iscroll.refresh(); // scrollview 상위에 선언된 iscroll 객체 refresh
};

ScrollView.prototype.scrollToComponent = function scrollToComponent(component) {
	this.iscroll.scrollToElement(component);
};

ScrollView.prototype.scrollTo = function scrollTo(x, y, runtime) {
	this.iscroll.scrollTo(x, y, runtime);
};

ScrollView.index = 1;

/*******************************************************************************
 * List
 ******************************************************************************/

var listTouchTolerance = 10;

/**
 * List는 화면에 여러 아이템을 한 화면에 리스트로 보여주는 컴퍼넌트입니다.
 * 
 * 지정된 컴퍼넌트 ID와 dataMapID를 가지고 List 컴퍼넌트를 생성합니다.
 * 
 * @constructor
 * @base Component
 */
var List = function List(id, dataMapId) {
	if (id == undefined)
		return;

	this.parent(id, dataMapId);

	this.scrollVeiw = null;
	this.iscroll = null;

	var parentComponent = this.parent;
	while (parentComponent != null) {
		if (parentComponent.componentName == "ScrollView") {
			this.iscroll = parentComponent.iscroll;
			this.scrollVeiw = parentComponent;
			break;

		}
		parentComponent = parentComponent.parent;
	}

	this.firstList = null;
	this.data = null;
	this.itemRenderer = null;
	this.columnRenderers = null;
	this.touchTolerance = 2;

	this.firstRow = this.element.getElementsByTagName('li')[0].cloneNode(true);
	if (this.element.getElementsByTagName('li')[1] != null) {
		this.secondRow = this.element.getElementsByTagName('li')[1]
				.cloneNode(true);
	}
	this.element.removeChild(this.element.getElementsByTagName('li')[0]);
	if (this.element.getElementsByTagName('li')[0] != null) {
		this.element.removeChild(this.element.getElementsByTagName('li')[0]);
	}
	this.data = [];
	this.itemRenderer = null;
	this.columnRenderers = new Map();
	this._onItemClickHandler = null;
	this._onItemLongClickHandler = null;

	this.itemFocusColor = "#4FB6FF";
	this.isTouch = ('ontouchstart' in window);

	this.normalStyleClass = this.firstRow.className;
	this.activeStyleClass = this.firstRow.className + " "
			+ this.firstRow.className + "Active";
	this.disableStyleClass = this.firstRow.className + " "
			+ this.firstRow.className + "Disable";

	this.mouseMoveFlag = false;
	this.longClickFlag = false;

	// Event sniffing
	this.START_EVENT = this.isTouch ? 'touchstart' : 'mousedown';
	this.MOVE_EVENT = this.isTouch ? 'touchmove' : 'mousemove';
	this.END_EVENT = this.isTouch ? 'touchend' : 'mouseup';

	this._ontouchstart = function(e) {
		// e.preventDefault();
		e.currentTarget.component.mouseMoveFlag = false;

		var pageX = e.currentTarget.component.isTouch ? e.changedTouches[0].pageX
				: e.pageX;
		var pageY = e.currentTarget.component.isTouch ? e.changedTouches[0].pageY
				: e.pageY;

		e.currentTarget.tid = setTimeout(function(obj, e) {
			return function() {
				obj.onItemLongClick(e);
			};
		}.apply(this.component, [ this.component, {
			target : e.target,
			currentTarget : e.currentTarget
		} ]), 600);

		e.currentTarget.startX = pageX;
		e.currentTarget.startY = pageY;

		e.currentTarget.style.backgroundColor = e.currentTarget.component.itemFocusColor;
		e.currentTarget.className = e.currentTarget.component.activeStyleClass;
	};
	this._ontouchmove = function(e) {
		// e.preventDefault();

		var pageX = e.currentTarget.component.isTouch ? e.changedTouches[0].pageX
				: e.pageX;
		var pageY = e.currentTarget.component.isTouch ? e.changedTouches[0].pageY
				: e.pageY;

		if (!e.currentTarget.component.mouseMoveFlag
				&& (pageX - e.currentTarget.startX > listTouchTolerance
						|| pageX - e.currentTarget.startX < -listTouchTolerance
						|| pageY - e.currentTarget.startY > listTouchTolerance || pageY
						- e.currentTarget.startY < -listTouchTolerance)) {
			e.currentTarget.component.mouseMoveFlag = true;
			e.currentTarget.style.backgroundColor = e.currentTarget.savedBackgroundColor;
			e.currentTarget.className = e.currentTarget.component.normalStyleClass;
			clearTimeout(e.currentTarget.tid);
		}

	};

	this.autoResize = false;
	this.autoResizeElemtns = null;

	this.formatters = null;
}.inherits(Component);

List.prototype.setFormatter = function setFormatter(formatters) {
	this.formatters = formatters;
};

/**
 * auto height resize
 * 
 * @param autoResize
 * 
 * @param elements
 * 
 */
List.prototype.setAutoResizeHeight = function(autoResize, elements) {
	this.autoResize = autoResize;
	this.autoResizeElemtns = elements;

	if (this.iscroll == null)
		return;

	if (autoResize) {
		this.onResizeDelegator = Delegate.create(this, this.onResize);
		window
				.addEventListener(
						(device.platformName == "iPhone" && !window.isSimulator) ? "orientationchange"
								: "resize", this.onResizeDelegator, false);
		this.onResize();
	} else {
		this.autoResize = false;
		this.autoResizeElemtns = null;
		window
				.removeEventListener(
						(device.platformName == "iPhone" && !window.isSimulator) ? "orientationchange"
								: "resize", this.onResizeDelegator);
	}
};

List.prototype.onResize = function(e) {
	var otherHeights = 0;

	for ( var i = 0; i < this.autoResizeElemtns.length; i++) {
		var el = this.autoResizeElemtns[i];
		if (el.getStyle("visibility") == "visible"
				&& el.getStyle("display") != "none") {
			otherHeights += el.getOuterHeight();
		}
	}

	var height = device.getWindowHeight() - otherHeights;
	this.scrollVeiw.setHeight(height + "px");
	this.iscroll.refresh(); // scrollview 상위에 선언된 iscroll 객체 refresh
};

/**
 * 포커스된 리스트 아이템의 배경색상을 지정합니다.
 * 
 * @param color
 *            색상
 */
List.prototype.setItemFocusColor = function(color) {
	this.itemFocusColor = color;
};
/**
 * 리스트 롱클릭 핸들러를 등록합니다.
 * 
 * @param onLongClick
 *            핸들러 함수
 */
List.prototype.setOnItemLongClickHandler = function(onLongClick) {
	this._onItemLongClickHandler = onLongClick;
};

/**
 * @private
 * @param e
 */
List.prototype.onItemLongClick = function(e) {
	e.currentTarget.component.longClickFlag = true;
	e.currentTarget.style.backgroundColor = e.currentTarget.savedBackgroundColor;
	e.currentTarget.className = e.currentTarget.component.normalStyleClass;

	clearTimeout(e.currentTarget.tid);
	if (e.currentTarget.component._onItemLongClickHandler != null)
		e.currentTarget.component._onItemLongClickHandler(e);
};
/**
 * 리스트 아이템이 클릭되었을 시 발생하는 이벤트 핸들러를 등록합니다.
 * 
 * @param onClick
 *            핸들러 함수
 */
List.prototype.setOnItemClickHandler = function(onClick) {
	this._onItemClickHandler = onClick;
};
List.prototype.onItemClickHandler = function(e) {
	// e.preventDefault();

	if (e.currentTarget.component.longClickFlag
			|| e.currentTarget.component.mouseMoveFlag) {
		e.currentTarget.component.longClickFlag = false;
		e.currentTarget.component.mouseMoveFlag = false;
		return;
	}

	clearTimeout(e.currentTarget.tid);
	e.currentTarget.style.backgroundColor = e.currentTarget.savedBackgroundColor;
	e.currentTarget.className = e.currentTarget.component.normalStyleClass;

	if (e.currentTarget.component._onItemClickHandler != null)
		e.currentTarget.component._onItemClickHandler(e);

};
/**
 * DataSet 타입으로 List 컴퍼넌트에 값을 지정합니다. 각각의 리스트 아이템의 데이터를 지정합니다.
 * 
 * @param array
 *            각 리스트 아이템의 배열, 각 배열값은 map 타입으로 정의되어 있습니다.
 */
List.prototype.setData = function(array) {
	this.removeAllItem();
	this.data = array;

	for ( var i = 0; i < array.length; i++) {
		var appendedList;

		if (i % 2 == 0)
			appendedList = this.firstRow.cloneNode(true);
		else if (i % 2 == 1 && this.secondRow != null)
			appendedList = this.secondRow.cloneNode(true);
		else
			appendedList = this.firstRow.cloneNode(true);

		var map = array[i];
		var key;

		for ( var j = 0; j < map.size(); j++) {
			key = map.getKeyByIndex(j);
			setDataByName(appendedList, key, map.get(key), this);
		}

		appendedList.index = i;
		appendedList.component = this;
		appendedList.savedBackgroundColor = appendedList.style.backgroundColor;
		appendedList.style.visibility = "visible";

		appendedList.addEventListener(this.START_EVENT, this._ontouchstart,
				false);
		appendedList
				.addEventListener(this.MOVE_EVENT, this._ontouchmove, false);
		appendedList.addEventListener(this.END_EVENT, this.onItemClickHandler,
				false);

		this.element.appendChild(appendedList);
	}
};

/**
 * 리스트에 새로운 데이터를 추가합니다.
 * 
 * @param array
 *            리스트 아이템의 배열, 각 배열값은 map 타입으로 정의되어 있습니다.
 */
List.prototype.addData = function(array) {
	var lenBefore = this.data.length;
	this.data = this.data.concat(array);
	for ( var i = 0; i < array.length; i++) {
		var appendedList;

		if (this.element.childNodes.length % 2 == 0) {
			appendedList = this.firstRow.cloneNode(true);
		} else if (this.element.childNodes.length % 2 == 1
				&& this.secondRow != null) {
			appendedList = this.secondRow.cloneNode(true);
		} else {
			appendedList = this.firstRow.cloneNode(true);
		}

		var map = array[i];
		var key;
		for ( var j = 0; j < map.size(); j++) {
			key = map.getKeyByIndex(j);
			setDataByName(appendedList, key, map.get(key), this);
		}

		appendedList.index = i + lenBefore;
		appendedList.component = this;
		appendedList.savedBackgroundColor = appendedList.style.backgroundColor;
		appendedList.style.visibility = "visible";

		appendedList.addEventListener(this.START_EVENT, this._ontouchstart,
				false);
		appendedList
				.addEventListener(this.MOVE_EVENT, this._ontouchmove, false);
		appendedList.addEventListener(this.END_EVENT, this.onItemClickHandler,
				false);

		this.element.appendChild(appendedList);
	}
};

/**
 * 특정 인덱스에 리스트 아이템을 추가합니다.
 * 
 * @param array
 *            array 리스트 아이템의 배열, 각 배열값은 map 타입으로 정의되어 있습니다.
 * @param targetIndex
 *            리스트 인덱스
 * @param rowStyleClass
 *            스타일 클래스 이름
 */
List.prototype.addDataAt = function(array, targetIndex, rowStyleClass) {
	lenBefore = this.data.length;
	this.data = this.data.concat(array);

	for ( var i = 0; i < array.length; i++) {
		var appendedList;

		if (this.element.childNodes.length % 2 == 0) {
			appendedList = this.firstRow.cloneNode(true);
		} else if (this.element.childNodes.length % 2 == 1
				&& this.secondRow != null) {
			appendedList = this.secondRow.cloneNode(true);
		} else {
			appendedList = this.firstRow.cloneNode(true);
		}

		if (rowStyleClass != undefined) {
			appendedList.className = rowStyleClass;
		}

		var map = array[i];
		var key;
		for ( var j = 0; j < map.size(); j++) {
			key = map.getKeyByIndex(j);
			setDataByName(appendedList, key, map.get(key), this);
		}

		appendedList.index = i + lenBefore;
		appendedList.component = this;
		appendedList.savedBackgroundColor = appendedList.style.backgroundColor;
		appendedList.style.visibility = "visible";

		appendedList.addEventListener(this.START_EVENT, this._ontouchstart,
				false);
		appendedList
				.addEventListener(this.MOVE_EVENT, this._ontouchmove, false);
		appendedList.addEventListener(this.END_EVENT, this.onItemClickHandler,
				false);

		var listAfter = this.element.childNodes(targetIndex + i);

		this.element.insertBefore(appendedList, listAfter);
	}

};

/**
 * 리스트의 리스트 아이템을 배열형식으로 리턴합니다.
 * 
 * @return array array 리스트 아이템의 배열, 각 배열값은 map 타입으로 정의되어 있습니다.
 */
List.prototype.getData = function() {
	return this.data;
};

/**
 * 리스트의 특정 인덱스의 데이터를 리턴합니다.
 * 
 * @return map map 형식으로 저장된 리스트 아이템값
 */
List.prototype.getDataAt = function(idx) {
	return this.data[idx];
};

/**
 * 리스트 아이템의 Renderer를 지정합니다.
 * 
 * @param itemRenderer
 *            스타일 클래스 이름
 */
List.prototype.setItemRenderer = function(itemRenderer) {
	this.itemRenderer = itemRenderer;
};

/**
 * Renderer 를 리턴합니다.
 * 
 * @return itemRenderer 스타일 클래스 이름
 */
List.prototype.getItemRenderer = function() {
	return this.itemRenderer;
};

/**
 * 특정 열의 Renderer를 지정합니다.
 */
List.prototype.addColumnRenderer = function(colRenderer) {
	this.columnRenderers.put(colRenderer.column, colRenderer);
	var rows = this.element.getElementsByTagName("li");
	for ( var i = 0; i < rows.length; i++) {
		var cols = rows[i].getElementsByTagName("div");
		for ( var j = 0; j < cols.length; j++) {
			if (cols[j].getAttribute != null
					&& colRenderer.column == cols[j].getAttribute("name")) {
				if (colRenderer.isStyleOnly) {
					if (typeof colRenderer.renderer == "object") {
						cols[j].className = colRenderer.renderer[this.getData()[i]
								.get(colRenderer.column)];
					} else {
						cols[j].className = colRenderer.renderer;
					}
				}
			}
		}
	}
	if (this.firstRow != null) {
		var cols = this.firstRow.getElementsByTagName("div");
		for ( var j = 0; j < cols.length; j++) {
			if (cols[j].getAttribute != null
					&& colRenderer.column == cols[j].getAttribute("name")) {
				if (colRenderer.isStyleOnly) {
					if (typeof colRenderer.renderer == "string") {
						cols[j].className = colRenderer.renderer;
					}
				}
			}
		}
	}
	if (this.secondRow != null) {
		var cols = this.secondRow.getElementsByTagName("div");
		for ( var j = 0; j < cols.length; j++) {
			if (cols[j].getAttribute != null
					&& colRenderer.column == cols[j].getAttribute("name")) {
				if (colRenderer.isStyleOnly) {
					if (typeof colRenderer.renderer == "string") {
						cols[j].className = colRenderer.renderer;
					}
				}
			}
		}
	}
};

/**
 * 특정열의 지정된 renderer를 리턴합니다.
 */
List.prototype.getColumnRenderer = function(column) {
	return this.columnRenderers.get(column);
};

/**
 * 리스트 아이템을 추가합니다.
 */
List.prototype.addItem = function(item) {
	this.element.appendChild(item);
};

/**
 * 특정 인덱스에 리스트 아이템을 추가합니다.
 */
List.prototype.addItemAt = function(idx, item) {
	var elem = this.element.childNodes[idx];
	this.element.insertBefore(item, elem);
};

/**
 * 특정 인덱스의 리스트 아이템을 리턴합니다.
 */
List.prototype.getItemAt = function(idx) {
	return this.element.childNodes[idx];
};

/**
 * 리스트 아이템을 제거합니다.
 */
List.prototype.removeItem = function(item) {
	this.element.removeChild(item);
};

/**
 * 특정 인덱스의 리스트 아이템을 제거합니다.
 */
List.prototype.removeItemAt = function(idx) {
	this.element.removeChild(this.element.childNodes[idx]);
};

/**
 * 모든 리스트 아이템을 제거합니다.
 */
List.prototype.removeAllItem = function() {
	while (this.element.childNodes.length > 0) {
		this.element.removeChild(this.element.childNodes[0]);
	}
};

/**
 * 리스트 컴퍼넌트의 데이터를 refresh 합니다.
 */
List.prototype.refresh = function() {
	this.setData(this.data);
};

/**
 * 모든 리스트 아이템들을 리턴합니다.
 */
List.prototype.getAllItems = function() {
	return this.element.getElementsByTagName("li");
};

/**
 * 
 */
var setDataByName = function setDataByName(element, name, value, list) {
	if (value != undefined) {
		if (element.attributes && element.getAttribute("name") == name) {
			var columnRenderer = list.getColumnRenderer(name);

			if (columnRenderer != null && columnRenderer.renderer != null) {
				if (columnRenderer.isStyleOnly) {
					if (typeof columnRenderer.renderer == "object")
						element.className = columnRenderer.renderer[value];
					else
						element.className = columnRenderer.renderer;
				} else {
					if (typeof columnRenderer.renderer == "object")
						element.className = columnRenderer.renderer[value];
					else
						element.className = columnRenderer.renderer;

					if (list.formatters != null) {
						if (list.formatters[name] != undefined)
							element.innerHTML = list.formatters[name]
									.format(value);
						else
							element.innerHTML = value;
					} else
						element.innerHTML = value;
				}
			} else {
				if (list.formatters != null) {
					if (list.formatters[name] != undefined)
						element.innerHTML = list.formatters[name].format(value);
					else
						element.innerHTML = value;
				} else
					element.innerHTML = value;
			}
		}

		var childNodes = element.childNodes;

		if (childNodes == undefined || childNodes == null)
			return;

		for ( var i = 0; i < childNodes.length; i++) {
			if (childNodes[i].nodeType == 3)
				continue;
			else if (childNodes[i].hasChildNodes())
				setDataByName(childNodes[i], name, value, list);
			else {
				// show image in list dynamically
				if (name.indexOf('image') >= 0
						&& childNodes[i].getAttribute('name') == name
						&& childNodes[i].getAttribute('class') == 'Image') {
					childNodes[i].style.backgroundPosition = "center center";
					childNodes[i].style.backgroundRepeat = "no-repeat";
					childNodes[i].style.backgroundImage = "url(" + value + ")";
				} else if (childNodes[i].attributes
						&& childNodes[i].getAttribute('name') == name) {
					var columnRenderer = list.getColumnRenderer(name);

					if (columnRenderer != null
							&& columnRenderer.renderer != null) {
						if (columnRenderer.isStyleOnly) {
							if (typeof columnRenderer.renderer == "object")
								childNodes[i].className = columnRenderer.renderer[value];
							else
								childNodes[i].className = columnRenderer.renderer;
						} else {
							if (typeof columnRenderer.renderer == "object")
								childNodes[i].className = columnRenderer.renderer[value];
							else
								childNodes[i].className = columnRenderer.renderer;

							if (list.formatters != null) {
								if (list.formatters[name] != undefined)
									childNodes[i].innerHTML = list.formatters[name]
											.format(value);
								else
									childNodes[i].innerHTML = value;
							} else
								childNodes[i].innerHTML = value;
						}
					} else {
						if (list.formatters != null) {
							if (list.formatters[name] != undefined)
								childNodes[i].innerHTML = list.formatters[name]
										.format(value);
							else
								childNodes[i].innerHTML = value;
						} else {
							if (childNodes[i].nodeName.toLowerCase() == "input") {
								childNodes[i].value = value;
							} else {
								childNodes[i].innerHTML = value;
							}
						}
					}
				}
			}
		}
	}
};

/**
 * 특정 이름의 컬럼을 안 보여주게 합니다.
 */
List.prototype.hideColumn = function(colName) {
	var rows = this.element.getElementsByTagName("li");
	for ( var i = 0; i < rows.length; i++) {
		var cols = rows[i].getElementsByTagName("div");
		for ( var j = 0; j < cols.length; j++) {
			if (cols[j].getAttribute != null
					&& colName == cols[j].getAttribute("name")) {
				cols[j].style.display = "none";
				break;
			}
		}
	}
};

/**
 * 특정 이름의 컬럼을 보여줍니다.
 */
List.prototype.showColumn = function(colName) {
	var rows = this.element.getElementsByTagName("li");
	for ( var i = 0; i < rows.length; i++) {
		var cols = rows[i].getElementsByTagName("div");
		for ( var j = 0; j < cols.length; j++) {
			if (cols[j].getAttribute != null
					&& colName == cols[j].getAttribute("name")) {
				cols[j].style.display = "block";
				break;
			}
		}
	}
};

/**
 * 
 */
function ColumnRenderer(column, renderer, styleOnly) {
	this.column = column;
	this.isStyleOnly = styleOnly;
	this.renderer = renderer;
}

// ================================================================================================================//
// ================================================== ComboBox
// =====================================================//
// ===============================================================================================================//

/**
 * ComboBox는 콤보박스 컴퍼넌트입니다.
 * 
 * @private
 * @constructor
 * @base Component
 */
var ComboBox = function ComboBox(id, dataMapId) {
	if (id == undefined)
		return;

	this.parent(id, dataMapId);

	/**
	 * 현재 선택된 아이템의 인덱스
	 * 
	 * @members
	 */
	this.selectedIndex;
	this.onItemClickHandler = null;
	this.itemArray = [];
	this.expanded = false;
}.inherits(Component);

/**
 * 아이템의 클릭 이벤트 핸들러를 등록합니다.
 */
ComboBox.prototype.setOnItemSelectHandler = function(onSelect) {
	this.onItemSelectHandler = onSelect;
};

/**
 * 콤보박스 리스트의 데이터를 지정합니다.
 */
ComboBox.prototype.setData = function(array) {
	this.itemArray = [];
	this.element.innerHTML = "";
	for ( var i = 0; i < array.length; i++) {
		var map = array[i];
		var comboItem = new ComboItem();
		comboItem.setData(map.get('label'));
		this.addItem(comboItem);
	}

	var selectedItem = this.itemArray[0];
	selectedItem.setOnClickHandler(function(e) {
		e.target.component.comboHost.expand();
	});
	this.element.appendChild(selectedItem.element);
	this.selectedIndex = 0;
};

ComboBox.prototype._onItemClickHandler = function(e) {
	e.target.component.comboHost.selectedIndex = e.target.component.index;
	e.target.component.comboHost.collapse();
};
/**
 * 콤보박스에 새로운 아이템을 추가합니다.
 * 
 * @param item
 *            추가할 아이템
 */
ComboBox.prototype.addItem = function(item) {
	item.comboHost = this;
	item.index = this.itemArray.length;
	this.itemArray.push(item);
};
/**
 * 지정된 인덱스에 새로운 아이템을 추가합니다.
 * 
 * @param idx
 *            인덱스
 * @param item
 *            새로운 아이템
 */
ComboBox.prototype.addItemAt = function(idx, item) {
	for ( var i = idx; i < this.itemArray.length; i++) {
		this.itemArray[i].index++;
	}
	item.comboHost = this;
	item.index = idx;
	this.itemArray.splice(idx, 0, item);
};
/**
 * 인자로 주어진 아이템을 콤보박스에서 삭제합니다.
 * 
 * @param item
 *            삭제할 아이템
 */
ComboBox.prototype.removeItem = function(item) {
	var pos = -1;
	for ( var i = 0; i < this.itemArray.length; i++) {
		if (this.itemArray[i] == item) {
			item.remove();
			pos = i;
		}
		if (pos > -1 && i > pos) {
			this.itemArray[i].index--;
		}
	}
	if (pos > 0) {
		this.itemArray.splice(pos, 1);
	}
};
/**
 * 주어진 인덱스에 위치한 아이템을 삭제합니다.
 * 
 * @param idx
 *            인덱스
 */
ComboBox.prototype.removeItemAt = function(idx) {
	this.removeItem(this.itemArray[idx]);
};

/**
 * 콤보박스를 오픈합니다.
 */
ComboBox.prototype.expand = function() {
	for ( var i = 0; i < this.itemArray.length; i++) {
		this.itemArray[i].setOnClickHandler(this._onItemClickHandler);
		this.element.appendChild(this.itemArray[i].element);
	}
	this.expanded = true;
};

/**
 * 콤보박스를 닫습니다.
 */
ComboBox.prototype.collapse = function() {
	if (this.element.hasChildNodes()) {
		while (this.element.childNodes.length >= 1) {
			this.element.removeChild(this.element.firstChild);
		}
	}
	var selectedItem = this.itemArray[this.selectedIndex];
	selectedItem.setOnClickHandler(function(e) {
		e.target.component.comboHost.expand();
	});
	this.element.appendChild(selectedItem.element);
	this.expanded = false;
};

/**
 * ComboBox 컴퍼넌트에 들어갈 아이템을 가리키는 컴퍼넌트입니다.
 * 
 * @param id
 *            컴퍼넌트 id
 * @para dataMapId
 * @constructor
 * @base Component
 */
var ComboItem = function ComboItem(id, dataMapId) {
	if (id == undefined)
		return;

	this.parent(id, dataMapId);
}.inherits(Component);

/**
 * 새로운 ComboItem을 생성합니다.
 * 
 * @returns 생성된 ComboItem
 */
ComboItem.prototype.createElement = function createElement() {
	var el = document.createElement("li");
	el.className = this.componentName;
	return el;
};

// ================================================================================================================//
// ================================================== CheckBox
// =====================================================//
// ===============================================================================================================//
/**
 * CheckBox 컴퍼넌트입니다.
 * 
 * 지정된 컴퍼넌트 ID를 가지고 CheckBox 컴퍼넌트를 생성합니다.
 * 
 * @constructor
 * @base Component
 */
var CheckBox = function CheckBox(id, dataMapId) {
	if (id == undefined)
		return;

	this.parent(id, dataMapId);
	/**
	 * checkbox 컴퍼넌트가 체크되었는지를 나타냅니다. check되어 있을 시 true.
	 * 
	 * @members
	 */
	this.checked = false;

	this.label = this.element.innerHTML;
	this.onClass = this.element.className + "Active";
	this.offClass = this.element.className;
	this.element.onclick = this._onClickHandler;

}.inherits(Component);

CheckBox.prototype._onClickHandler = function(event) {
	this.component.check((this.component.checked) ? false : true);
	if (this.component.onClickHandler != null) {
		this.component.onClickHandler(event);
	}
};

/**
 * 클릭 이벤트 핸들러를 지정합니다.
 * 
 * @param onSelect
 *            체크박스가 클릭되었을 시 실행되는 함수
 */
CheckBox.prototype.setOnClickHandler = function(onSelect) {
	this.onClickHandler = onSelect;
};

/**
 * 인자로 주어진 상태에 따라 설정합니다.
 * 
 * @param checked
 *            boolean 값 (true -> check, false -> uncheck)
 */
CheckBox.prototype.check = function(checked) {
	this.checked = checked;
	if (this.checked) {
		this.element.className = this.onClass;
	} else {
		this.element.className = this.offClass;
	}
};

/**
 * 체크박스 옆에 출력될 문자열을 지정합니다.
 * 
 * @param labelText
 *            보여질 문자열
 */
CheckBox.prototype.setLabel = function(labelText) {
	var child = this.element.childNodes;
	for ( var i = 0; i < child.length; i++) {
		if (child[i].className == 'Label')
			child[i].innerHTML = labelText;
	}
};

// ================================================================================================================//
// ================================================== ToolBar
// =====================================================//
// ===============================================================================================================//
/**
 * ToolBar 는 화면 상단 또는 하단에 위치하면서 여러개의 버튼으로 구성된 컴퍼넌트입니다. 지정된 컴퍼넌트 ID를 가지고 ToolBar
 * 컴퍼넌트를 생성합니다.
 * 
 * @constructor
 * @base Component
 */
var ToolBar = function ToolBar(id, dataMapId) {
	if (id == undefined)
		return;

	this.parent(id, dataMapId);
	this.itemList = [];
}.inherits(Component);
/**
 * ToolBar에 새로운 아이템을 추가합니다.
 * 
 * @param item
 *            추가할 아이템
 */
ToolBar.prototype.addItem = function(item) {
	this.element.appendChild(item.element);
	this.itemList.push(item);
};
/**
 * ToolBar에서 아이템을 삭제합니다.
 * 
 * @param item
 *            삭제할 아이템
 */
ToolBar.prototype.removeItem = function(item) {
	for ( var i = 0; i < this.itemList.length; i++) {
		if (this.itemList[i] == item) {
			item.remove();
			this.itemList.splice(i, 1);
			return;
		}
	}
};

/**
 * 지정된 인덱스에 있는 아이템을 리턴합니다.
 * 
 * @param idx
 *            인덱스
 */
ToolBar.prototype.getItemAt = function(idx) {
	return this.element.getElementsByTagName("li")[idx];
};

/**
 * ToolBar 컴퍼넌트에 들어갈 아이템을 가리키는 컴퍼넌트입니다.
 * 
 * @base Component
 * @constructor
 */
var ToolBarItem = function ToolBarItem(id, dataMapId) {
	this.parent(id, dataMapId);
}.inherits(Component);

// ================================================================================================================//
// ================================================== TitleBar
// =====================================================//
// ===============================================================================================================//

/**
 * 타이틀바는 각 페이지의 제목과 이동버튼을 포함하는 컴퍼넌트입니다.
 * 
 * @param id
 *            컴퍼넌트 id
 * @param dataMapId
 * @constructor
 * @base Component
 */
var TitleBar = function TitleBar(id, dataMapId) {
	if (id == undefined)
		return;

	this.parent(id, dataMapId);
}.inherits(Component);

// ================================================================================================================//
// ================================================== TabBar
// =====================================================//
// ===============================================================================================================//
/**
 * TabBar는 여러개의 toggle 버튼으로 구성되어 지고 각각의 toggle 버튼을 클릭시 해당하는 내용물을 보여줍니다.
 * 
 * @param id
 *            컴퍼넌트 ID
 * @param dataMapId
 * @constructor
 * @base Component
 */
var TabBar = function TabBar(id, dataMapId) {
	if (id == undefined)
		return;

	this.parent(id, dataMapId);
	/**
	 * 현재 선택된 탭 버튼의 인덱스 번호
	 * 
	 * @members
	 */
	this.selectedIndex = -1;
	/**
	 * 탭바의 총 탭 버튼 수량
	 * 
	 * @members
	 */
	this.totalCount = -1;
	this.tabList = [];
	this.onTabChangeHandler = null;

	this.enabled = true;

	this.initTabs();
}.inherits(Component);

/**
 * 새로운 toggle 버튼을 추가합니다.
 * 
 * @param tgBtn
 *            새로운 toggle 버튼
 */
TabBar.prototype.addTab = function(tgBtn) {

	tgBtn.tabHost = this;
	tgBtn.index = this.tabList.length;

	tgBtn.setOnChangeHandler(this._onTabChangeHandler);
	if (tgBtn.element.parentNode == null) {
		this.element.appendChild(tgBtn.element);
	}
	this.tabList.push(tgBtn);
};

TabBar.prototype.setEnabled = function setEnabled(flag) {
	this.enabled = flag;
	if (!flag) {
		if (this.selectedIndex > -1) {
			this.tabList[this.selectedIndex].toggle();
			this.selectedIndex = -1;
			for ( var i = 0; i < this.tabList.length; i++) {
				this.tabList[i].setEnabled(false);
				this.tabList[i].setOnChangeHandler(null);
			}
		}
	} else {
		for ( var i = 0; i < this.tabList.length; i++) {
			this.tabList[i].setEnabled(true);
			this.tabList[i].setOnChangeHandler(this._onTabChangeHandler);
		}
	}
};

TabBar.prototype.initTabs = function() {
	var child = this.element.childNodes;
	var cnt = child.length;
	for ( var i = 0; i < cnt; i++) {
		if (child[i].className == 'Toggle' || child[i].title == "Toggle") {
			if (child[i].id == undefined) {
				child[i].id = "tab" + this.tabList.length;
			}
			var newToggle = new Toggle(child[i].id, child[i].name);
			newToggle.tabHost = this;
			newToggle.index = this.tabList.length;

			newToggle.setOnChangeHandler(this._onTabChangeHandler);

			this.tabList.push(newToggle);
		}
	}
};

/**
 * 주어진 인덱스에 toggle 버튼을 추가합니다.
 * 
 * @param idx
 *            인덱스
 * @param tgBtn
 *            toggle 버튼
 */
TabBar.prototype.addTabAt = function(idx, tgBtn) {
	tgBtn.tabHost = this;
	tgBtn.index = idx;
	tgBtn.setOnChangeHandler(this._onTabChangeHandler);
	for ( var i = idx; i < this.tabList.length; i++) {
		this.tabList[i].index++;
	}
	if (tgBtn.element.parentNode == null) {
		this.element.insertBefore(tgBtn.element, this.tabList[idx].element);
	}
	this.tabList.splice(idx, 0, tgBtn);

};

TabBar.prototype._onTabChangeHandler = function(event) {
	var tabhost = this.tabHost;
	if (tabhost.selectedIndex > -1) {
		tabhost.tabList[tabhost.selectedIndex].toggle();
	}
	tabhost.selectedIndex = this.element.component.index;
	var e = {
		target : event.target,
		currentTarget : event.target,
		index : tabhost.selectedIndex
	};
	if (tabhost.onTabChangeHandler != null)
		tabhost.onTabChangeHandler(e);
};

/**
 * TabBar에서 toggle 버튼을 삭제합니다.
 * 
 * @param tgBtn
 *            삭제될 toggle 버튼
 */
TabBar.prototype.removeTab = function(tgBtn) {
	var pos = -1;
	for ( var i = 0; i < this.tabList.length; i++) {
		if (this.tabList[i] == tgBtn) {
			tgBtn.remove();
			pos = i;
		}
		if (pos > -1 && i > pos) {
			this.tabList[i].index--;
		}
	}
	if (pos > 0) {
		this.tabList.splice(pos, 1);
	}

};
/**
 * 인덱스에 위치한 toggle 버튼을 삭제합니다.
 * 
 * @param idx
 *            인덱스
 */
TabBar.prototype.removeTabAt = function(idx) {
	if (idx < this.tabList.length) {
		this.removeTab(this.tabList[idx]);
	}
};

/**
 * 현재 선택된 toggle 버튼을 리턴합니다.
 * 
 * @reutrn 현재 선택된 toggle 버튼
 */
TabBar.prototype.getCurrentTab = function() {
	return this.tabList[this.selectedIndex];
};

TabBar.prototype.getTabAt = function(idx) {
	return this.tabList[idx];
};

/**
 * 인덱스의 toggle 버튼을 리턴합니다.
 * 
 * @param idx
 *            인덱스
 * @return tgBtn 인덱스에 위치한 toggle 버튼
 */
TabBar.prototype.getCurrentTabIndex = function(idx) {
	return this.selectedIndex;
};

/**
 * 인자로 주어진 인덱스의 toggle 버튼을 선택합니다.
 * 
 * @param idx
 *            인덱스
 */
TabBar.prototype.select = function(idx) {
	if (!this.enabled)
		return;

	// IDE builder에서 Toggle이 자동 선언 되면서 hadler가 reset 되어버려서 임시로 삽입
	for ( var i = 0; i < this.tabList.length; i++) {
		this.tabList[i].setOnChangeHandler(this._onTabChangeHandler);
		this.tabList[i].element.component = this.tabList[i];
	}

	var tab = this.getTabAt(idx);
	if (this.selectedIndex > -1) {
		this.tabList[this.selectedIndex].toggle();
	}
	tab.toggle();
	this.selectedIndex = tab.index;
	var event = {
		target : tab,
		currentTarget : tab,
		index : this.selectedIndex
	};

	if (this.onTabChangeHandler != null)
		this.onTabChangeHandler(event);
};

/**
 * 새로운 toggle 버튼이 눌러졌을시 발생하는 이벤트의 핸들러를 등록합니다.
 * 
 * @param onChange
 *            핸들러 함수
 */
TabBar.prototype.setTabChangeHandler = function(onChange) {
	this.onTabChangeHandler = onChange;
};

// ================================================================================================================//
// ================================================== Toggle
// =====================================================//
// ===============================================================================================================//

/**
 * Toggle은 두가지 옵션을 선택할 수 있게 해주는 컴퍼넌트입니다. 예를 들어 true 또는 false. 지정된 컴퍼넌트 ID 를 가지고
 * Toggle 컴퍼넌트를 생성합니다.
 * 
 * @constructor
 * @base Component
 */
var Toggle = function Toggle(id, dataMapId) {
	if (id == undefined)
		return;
	this.parent(id, dataMapId);

	/**
	 * @member Toggle 컴퍼넌트가 선택되어
	 */
	this.enabled = true;
	this.checked = false;
	this.onChangeHandler = null;
	this.onClass = this.element.className + "Active";
	this.offClass = this.element.className;
	this.element.onclick = this._onClickHandler;
}.inherits(Component);

Toggle.prototype.setEnabled = function setEnable(flag) {
	this.enabled = flag;
}

/**
 * 현재 설정된 값의 반대값으로 설정합니다. 현재 값이 True 인 경우 false 로 설정
 */
Toggle.prototype.toggle = function() {
	if (!this.enabled)
		return;
	this.checked = (this.checked) ? false : true;
	if (this.checked) {
		this.element.className = this.onClass;
	} else {
		this.element.className = this.offClass;
	}
};

/**
 * @private
 * @param event
 */
Toggle.prototype._onClickHandler = function(event) {
	this.component.toggle();
	if (this.component.onChangeHandler != null) {
		this.component.onChangeHandler(event);
	}
};

/**
 * Toggle 값이 변경되었을 시 발생하는 이벤트 핸들러를 등록합니다.
 * 
 * @param handler
 *            핸들러 함수
 */
Toggle.prototype.setOnChangeHandler = function(handler) {
	this.onChangeHandler = handler;
};

// ================================================================================================================//
// ================================================== Menu
// =====================================================//
// ===============================================================================================================//
/**
 * Menu 는 보통 메뉴 상단 또는 하단에 위치한 버튼들로 구성되어 있는 컴퍼넌트입니다. 하나의 메뉴에 여러개의 메뉴 아이템이 포함될 수
 * 있습니다. 아이폰의 경우 일반 HTML 메뉴같은 형식을 가지고 있고, 안드로이드의 경우 메뉴 버튼을 눌렀을시 나오는 메뉴 형식으로
 * 구성됩니다.
 * 
 * 지정된 컴퍼넌트 ID 를 가지고 Menu 컴퍼넌트를 생성합니다.
 * 
 * @constructor
 * @base Component
 */
var Menu = function Menu(id, dataMapId) {
	if (id == undefined)
		return;
	this.parent(id, dataMapId);

	if (device.platformName == "iPhone") {
		this.removeAll();
		this.element.style.width = "100%";
	}
}.inherits(Component);

/**
 * 메뉴의 배경화면 이미지를 지정합니다.
 */
Menu.prototype.setBackgroundImage = function(path) {
	this.element.style.backgroundImage = "url(" + path + ")";
};

/**
 * 메뉴 아이템을 추가합니다.
 */
Menu.prototype.add = function(title, callback, elementId, image) {
	if (device.platformName == "Android" || device.platformName == "BlackBerry") {
		if (image == "" || image == undefined || image == null
				|| image == "undefined" || image == "null")
			image = "";

		if (window.isSimulator)
			nativeUID.add(title, GetFunctionName(callback));
		else
			nativeUID.add(title, GetFunctionName(callback), image);
	} else if (device.platformName == "iPhone") {
		var menuItem = document.createElement("div");

		menuItem.setAttribute("id", elementId);
		menuItem.setAttribute("class", "MenuItem");
		menuItem.setAttribute("onclick", "javascript:"
				+ GetFunctionName(callback) + "();");
		menuItem.setAttribute("title", title);
		// menuItem.style.height = "43px";
		menuItem.style.float = "left";
		menuItem.style.marginRight = "2%";

		this.element.appendChild(menuItem);

		var elements = this.element.getElementsByTagName("div");
		var n = elements.length;

		if (n > 0)
			elements[0].style.marginLeft = "2%";

		for ( var i = 0; i < n; i++)
			elements[i].style.width = ((100 - (2 * n) - 2) / n) + "%";
	}
};

/**
 * 인자로 주어진 이름을 가진 아이템을 삭제합니다.
 */
Menu.prototype.removeByTitle = function(title) {
	if (device.platformName == "Android" || device.platformName == "BlackBerry") {
		nativeUID.removeByTitle(title);
	} else if (device.platformName == "iPhone") {
		var elements = this.element.getElementsByTagName("div");
		var n = elements.length;

		for ( var i = 0; i < n; i++) {
			if (elements[i].className == "MenuItem") {
				if (elements[i].title == title) {
					this.element.removeChild(elements[i]);

					break;
				}
			}
		}

		// Reset width for each menu items.
		n = elements.length;

		log.log("Menu size : " + n);

		for ( var i = 0; i < n; i++)
			elements[i].style.width = ((100 - (2 * n) - 2) / n) + "%";
	}
};

/**
 * 모든 메뉴 아이템들을 삭제합니다.
 */
Menu.prototype.removeAll = function() {
	if (device.platformName == "Android" || device.platformName == "BlackBerry")
		nativeUID.removeAll();
	else if (device.platformName == "iPhone") {
		var elements = this.element.getElementsByTagName("div");

		for ( var i = elements.length - 1; i > -1; i--) {
			if (elements[i].className == "MenuItem")
				this.element.removeChild(elements[i]);
		}
	}
};

// ================================================================================================================//
// ================================================== Table
// ======================================================//
// ===============================================================================================================//
/**
 * 테이블을 나타내는 컴퍼넌트 입니다.
 * 
 * @constructor
 * @base LayoutComponent
 * @param id
 *            컴퍼넌트 아이디
 * @param dataMapId
 */
var Table = function Table(id, dataMapId) {
	if (id == undefined)
		return;

	this.data;// set의 인자로 넘어온 전체 data
	this.parent(id, dataMapId);
	this.rows = [];
	this.hasHeader = false;
	this.header;

	var rowElements = this.element.getElementsByTagName("tr");
	if (rowElements != undefined && rowElements.length != 0) {
		if (rowElements[0].childNodes[0].tagName == "TH"
				|| rowElements[0].childNodes[0].tagName == "th") {
			this.header = rowElements[0];
			if (rowElements.length > 1)
				this.firstRow = rowElements[1].cloneNode(true);
			this.hasHeader = true;
		} else {
			this.firstRow = rowElements[0].cloneNode(true);
		}
	}

	for ( var i = 0; i < rowElements.length; i++) {

		if (rowElements[i].id == undefined || rowElements[i].id == "") {
			rowElements[i].id = this.id + "_" + "row" + "_" + i;
		}
		var row = new TableRow(rowElements[i].id, rowElements[i]
				.getAttribute("name"));
		row.parent = this;
		this.rows.push(row);
	}
}.inherits(LayoutComponent);

/**
 * 테이블에 행을 추가합니다.
 * 
 * @param row
 *            추가될 행
 */
Table.prototype.addRow = function addRow(row) {
	if (!this.hasHeader && row.isHeader) {
		this.hasHeader = true;
		this.header = row;

		this.rows.splice(0, 0, row);
		if (this.rows.length == 1)
			this.element.appendChild(row.element);
		else {
			this.element.insertBefore(row.element, this.rows[1].element);
		}

	} else if (!row.isHeader) {
		this.rows.push(row);
		this.element.appendChild(row.element);
	}
};

/**
 * 새로운 테이블 HTML 엘리먼트를 생성하고 리턴합니다.
 * 
 * @returns 새로 생성된 테이블 HTML 엘리먼트
 */
Table.prototype.createElement = function createElement() {
	var el = document.createElement("table");
	el.className = this.componentName;
	return el;
};

/**
 * 주어진 인덱스에 행을 추가합니다.
 * 
 * @param pos
 *            인덱스
 * @param row
 *            추가될 행
 */
Table.prototype.addRowAt = function addRowAt(pos, row) {
	if (!this.hasHeader && row.isHeader) {
		this.hasHeader = true;
		this.header = row;

		this.rows.splice(0, 0, row);
		if (this.rows.length == 1)
			this.element.appendChild(row.element);
		else
			this.element.insertBefore(row.element, this.rows[1].element);

	} else if (!row.isHeader) {
		if (this.hasHeader)
			pos++;
		this.rows.splice(pos, 0, row);
		if (this.rows.length == 1)
			this.element.appendChild(row.element);
		else
			this.element.insertBefore(row.element, this.rows[pos + 1].element);
	}
};

/**
 * 주어진 인덱스에 존재하는 행을 리턴합니다.
 * 
 * @param pos
 *            인덱스
 * @returns 해당되는 인덱스의 행
 */
Table.prototype.getRowAt = function getRowAt(pos) {
	return this.rows[pos];
};

/**
 * 주어진 아이디를 이용하여 행을 가져옵니다.
 * 
 * @param id
 *            행의 아이디
 * @returns 인자의 아이디와 일치하는 행
 */
Table.prototype.getRowById = function getRowById(id) {
	for ( var i = 0; i < this.rows.length; i++) {
		if (this.rows[i].id == id) {
			return this.rows[i];
		}
	}

	return null;
};

/**
 * 인자로 주어진 행을 테이블에서 삭제합니다.
 * 
 * @param row
 *            테이블 행 컴퍼넌트
 */
Table.prototype.removeRow = function removeRow(row) {
	var pos = -1;
	for ( var i = 0; i < this.rows.length; i++) {
		if (this.row == this.rows[i]) {
			pos = i;
		}
	}
	if (pos > -1) {
		this.removeRowAt(pos);
	}
};

/**
 * 테이블내의 데이터를 삭제 및 초기화합니다.
 * 
 * @function
 */
Table.prototype.reset = function reset() {
	this.element.innerHTML = "";
	this.rows = [];
};

/**
 * 인자로 주어진 데이터를 테이블에 입력합니다.
 * 
 * @param data
 *            테이블에 들어갈 데이터. Array, Map, DataSet의 인자로 들어올 수 있으며 타입에 맞게 테이블에
 *            입력됩니다.
 */
Table.prototype.setRowsData = function setRowsData(data) {
	this.data = data;

	if (data instanceof Array) {
		this.setRowsArrayData(data);
	} else if (data instanceof Map || data instanceof DataSet) {
		this.setRowsMapData(data);
	}
};
/**
 * Array 타입 형식으로 주어지는 데이터 인자를 테이블에 입력합니다.
 * 
 * @param arr
 *            데이블에 들어갈 데이터. Array 타입.
 */
Table.prototype.setHeaderMapData = function setHeaderMapData(map) {
	if (this.hasHeader)
		this.rows[0].setData(map);

};

Table.prototype.setRowsArrayData = function setRowsArrayData(arr) {

	if (this.hasHeader) {
		for ( var i = 1; i < this.rows.length; i++) {
			this.removeRowAt(i);
		}
	} else {
		this.reset();
	}

	this.data = arr;
	for ( var i = 0; i < arr.length; i++) {
		var appendedRow;
		appendedRow = this.firstRow.cloneNode(true);
		var map = arr[i];
		var key;
		for ( var j = 0; j < map.size(); j++) {
			key = map.getKeyByIndex(j);
			setTableDataByName(appendedRow, key, map.get(key), this);
		}

		appendedRow.id = this.id + "_" + "row" + "_" + i
				+ (this.hasHeader ? 1 : 0);

		this.element.appendChild(appendedRow);
		var row = new TableRow(appendedRow.id, appendedRow.getAttribute("name"));

		row.parent = this;
		this.rows.push(row);
	}
};
setTableDataByName = function(element, name, value, list) {
	if (element.attributes && element.getAttribute('name') == name) {
		element.innerHTML = value;
	}
	var childNodes = element.childNodes;
	if (childNodes == undefined || childNodes == null)
		return;
	for ( var i = 0; i < childNodes.length; i++) {
		if (childNodes[i].nodeType == 3) {
			continue;
		} else if (childNodes[i].hasChildNodes()) {
			this.setTableDataByName(childNodes[i], name, value, list);
		} else {
			if (childNodes[i].attributes
					&& childNodes[i].getAttribute('name') == name) {
				childNodes[i].innerHTML = value;
			}
		}
	}
};

/**
 * Map 타입 형식으로 주어지는 데이터 인자를 테이블에 입력합니다.
 * 
 * @param map
 *            데이블에 들어갈 데이터. Map 타입.
 */
Table.prototype.setRowsMapData = function setRowsMapData(map) {
	for ( var i = 0; i < this.rows.length; i++) {
		for ( var j = 0; j < this.rows[i].cells.length; j++) {
			var cell = this.rows[i].cells[j];
			if (map.get(cell.dataMapId) != undefined) {
				cell.setData(map.get(cell.dataMapId));
			}
		}
	}
};

/**
 * 인자로 주어진 인덱스에 위치한 행을 삭제합니다.
 * 
 * @param pos
 *            인덱스
 */
Table.prototype.removeRowAt = function removeRowAt(pos) {
	if (this.hasHeader && pos == 0) {
		this.hasHeader = false;
		this.header = null;
	}
	this.rows[pos].remove();
	this.rows.splice(pos, 1);
};

/**
 * 테이블의 행을 나타내는 컴퍼넌트입니다.
 * 
 * @constructor
 * @base LayoutComponent
 * @param id
 *            컴퍼넌트 id
 * @dataMapId
 */
var TableRow = function TableRow(id, dataMapId) {
	if (id == undefined)
		return;
	this.parent(id, dataMapId);
	this.cells = [];
	this.headers = [];
	this.isHeader = false;

	var cellElements = this.element.getElementsByTagName("td");
	for ( var i = 0; i < cellElements.length; i++) {
		if (cellElements[i].id == undefined)
			cellElements[i].id = this.id + "_" + "cell" + "_" + i;
		var cell = new TableCell(cellElements[i].id, cellElements[i]
				.getAttribute('name'));
		this.cells.push(cell);
	}
	var headerElements = this.element.getElementsByTagName("th");
	for ( var i = 0; i < headerElements.length; i++) {
		this.isHeader = true;
		if (headerElements[i].id == undefined || headerElements[i].id == "")
			headerElements[i].id = this.id + "_" + "headerElements" + "_" + i;
		var header = new TableHeader(headerElements[i].id, headerElements[i]
				.getAttribute('name'));
		this.headers.push(header);
	}
}.inherits(LayoutComponent);

/**
 * 테이블의 행을 생성하고 리턴합니다.
 * 
 * @returns 새로 생성된 테이블 행
 */
TableRow.prototype.createElement = function createElement() {
	var el = document.createElement("tr");
	el.className = this.componentName;
	return el;
};

/**
 * 테이블의 행에 cell을 추가합니다.
 * 
 * @param cell
 *            추가될 cell
 */
TableRow.prototype.addCell = function addCell(cell) {
	this.cells.push(cell);
	this.element.appendChild(cell.element);
};

/**
 * TableHeader
 * 
 * @param header
 */
TableRow.prototype.addHeader = function addHeader(header) {
	this.isHeader = true;
	this.headers.push(header);
	this.element.appendChild(header.element);
};

/**
 * 인자로 주어진 인덱스에 cell을 추가합니다.
 * 
 * @param pos
 *            인덱스
 * @param cell
 *            추가될 cell
 */
TableRow.prototype.addCellAt = function addCellAt(pos, cell) {
	this.cells.splice(pos, 0, cell);
	if (this.cells.length == 1)
		this.element.appendChild(cell.element);
	else
		this.element.insertBefore(cell.element, this.cells[pos + 1].element);
};

/**
 * TableHeader
 * 
 * @param pos
 * @param header
 */
TableRow.prototype.addHeaderAt = function addHeaderAt(pos, header) {
	this.isHeader = true;
	this.headers.splice(pos, 0, header);
	if (this.headers.length == 1)
		this.element.appendChild(header.element);
	else
		this.element
				.insertBefore(header.element, this.headers[pos + 1].element);
};

/**
 * 주어진 인덱스의 cell을 리턴합니다.
 * 
 * @param pos
 *            인덱스
 * @returns 인덱스에 위치한 cell
 */
TableRow.prototype.getCellAt = function getCellAt(pos) {
	return this.cells[pos];
};

/**
 * TableHeader
 * 
 * @param pos
 * @returns
 */
TableRow.prototype.getHeaderAt = function getHeaderAt(pos) {
	return this.headers[pos];
};

/**
 * 테이블 행에 cell을 삭제합니다.
 * 
 * @param cell
 *            삭제할 cell
 */
TableRow.prototype.removeCell = function removeCell(cell) {
	var pos = -1;
	for ( var i = 0; i < this.cells.length; i++) {
		if (cell == this.cells[i]) {
			pos = i;
		}
	}
	if (pos > -1) {
		this.removeCellAt(pos);
	}
};

/**
 * TableHeader
 * 
 * @param header
 */
TableRow.prototype.removeHeader = function removeHeader(header) {
	var pos = -1;
	for ( var i = 0; i < this.headers.length; i++) {
		if (header == this.headers[i]) {
			pos = i;
		}
	}
	if (pos > -1) {
		this.removeHeaderAt(pos);
	}
	if (this.headers.length == 0)
		this.isHeader = false;
};

/**
 * 테이블 행에서 주어진 인덱스의 cell을 삭제합니다.
 * 
 * @param pos
 *            인덱스
 */
TableRow.prototype.removeCellAt = function removeCellAt(pos) {
	this.cells[pos].remove();
	this.cells.splice(pos, 1);
};
/**
 * TableHeader
 */
TableRow.prototype.removeHeaderAt = function removeHeaderAt(pos) {
	this.headers[pos].remove();
	this.headers.splice(pos, 1);
	if (this.headers.length == 0)
		this.isHeader = false;
};

/**
 * Map형식의 데이터를 테이블 행에 입력합니다.
 * 
 * @param map
 *            테이블에 들어갈 Map 형식의 데이터
 */
TableRow.prototype.setData = function setData(map) {
	if (this.isHeader) {
		for ( var i = 0; i < this.headers.length; i++) {
			if (map.get(this.headers[i].dataMapId) != undefined) {
				this.headers[i].setData(map.get(this.headers[i].dataMapId));
			}
		}
	} else {
		for ( var i = 0; i < this.cells.length; i++) {
			if (map.get(this.cells[i].dataMapId) != undefined) {
				this.cells[i].setData(map.get(this.cells[i].dataMapId));
			}
		}
	}

};

/**
 * 테이블 cell을 가리키는 컴퍼넌트입니다.
 * 
 * @constructor
 * @base LayoutComponent
 * @param id
 *            컴퍼넌트 id
 * @param dataMapId
 */
var TableCell = function TableCell(id, dataMapId) {
	if (id == undefined)
		return;
	this.parent(id, dataMapId);
}.inherits(LayoutComponent);

/**
 * 새로운 테이블 cell을 생성하고 리턴합니다.
 * 
 * @returns 새로 생성된 테이블 cell
 */
TableCell.prototype.createElement = function createElement() {
	var el = document.createElement("td");
	el.className = this.componentName;
	return el;
};

/**
 * 테이블의 헤더를 가리키는 컴퍼넌트입니다.
 * 
 * @constructor
 * @base LayoutComponent
 * @param id
 *            컴퍼넌트 id
 * @param dataMapId
 */
var TableHeader = function TableHeader(id, dataMapId) {
	this.parent(id, dataMapId);
}.inherits(LayoutComponent);

/**
 * 새로운 테이블 헤더 컴퍼넌트를 생성하고 리턴합니다.
 * 
 * @returns 생성된 테이블 헤더 컴퍼넌트
 */
TableHeader.prototype.createElement = function createElement() {
	var el = document.createElement("th");
	el.className = this.componentName;
	return el;
};

// ===============================================================================================================//
// ================================================== UI Util
// ====================================================//
// ===============================================================================================================//

function UI() {
	this.container = null;
}

UI.prototype.showProgress = function(id) {
	this.container = document.getElementById(id);
	this.container.style.visibility = "visible";
	clearInterval(this.tid);
	this.startProgressAnimation();
};

UI.prototype.startProgressAnimation = function() {
	this.tick = 0;
	if (this.tid != undefined)
		clearInterval(this.tid);
	this.tid = setInterval(Delegate.create(this, this.animateProgress), 80);

	var platform = ".";
	if (device.platformName == "simulator") {
		platform = "default";
	}
	this.container.style.backgroundImage = "url(../res/" + platform
			+ "/image/common/progress.png)";
};

UI.prototype.animateProgress = function() {
	switch (this.tick % 8) {
	case 0:
		this.container.style.backgroundPosition = "32px 0px";
		this.tick = 0;
		break;
	case 1:
		this.container.style.backgroundPosition = "64px 0px";
		break;
	case 2:
		this.container.style.backgroundPosition = "96px 0px";
		break;
	case 3:
		this.container.style.backgroundPosition = "128px 0px";
		break;
	case 4:
		this.container.style.backgroundPosition = "160px 0px";
		break;
	case 5:
		this.container.style.backgroundPosition = "192px 0px";
		break;
	case 6:
		this.container.style.backgroundPosition = "224px 0px";
		break;
	case 7:
		this.container.style.backgroundPosition = "0px 0px";
		break;
	}

	this.tick++;
};

UI.prototype.dismissProgress = function(id) {
	this.container = document.getElementById(id);
	this.tick = 0;
	this.container.style.visibility = "hidden";
	this.container.style.backgroundPosition = "0px 0px";
	clearInterval(this.tid);
};

/**
 * Carousel3D는 3D 효과를 위한 컴퍼넌트입니다.
 * 
 * @constructor
 */
var Carousel3D = function Carousel3D(id, dataMapId) {
	this.parent(id, dataMapId);

	this.radius = 10;
	this.side = 0;
	if (device.platformName == "Android")
		this.side = 150;
	else if (device.platformName == "iPhone")
		this.side = 130;
	this.long_side = 700;
	this.offset_x = 0;
	this.offset_y = 0;
	this.pop_x = 0;
	this.pop_y = 0;
	this.offset = 0;
	this.display_offset = 0;
	this.queue = [];
	this.flipping = false;
	this.coverflowCnt = 0;

	this._onCarouselClickHandler = null;
	this.moveCenterFlag = false;

	this._ontouchend = function(e) {
		if (e.currentTarget.moved)
			return;

		if (e.currentTarget.component._onCarouselClickHandler != null
				&& e.currentTarget.component._onCarouselClickHandler != "undefined") {
			e.currentTarget.component._onCarouselClickHandler((-1)
					* (e.currentTarget.component.offset - 3));
			e.currentTarget.moved = true;
		}

		e.stopPropagation();
	};

	this._ontouchstart = function(e) {
		e.preventDefault();
		e.currentTarget.moved = false;

		e.currentTarget.prevX = e.touches[0].pageX;
		e.currentTarget.prevY = e.touches[0].pageY;
	};

	this._ontouchmove = function(e) {
		e.preventDefault();

		if (e.touches[0].pageX - e.currentTarget.prevX < -20) {
			if (e.currentTarget.moved == false) {
				e.currentTarget.component.slide(-1);
				e.currentTarget.moved = true;
			}
		}

		if (e.touches[0].pageX - e.currentTarget.prevX > 20) {
			if (e.currentTarget.moved == false) {
				e.currentTarget.component.slide(1);
				e.currentTarget.moved = true;
			}
		}
	};

	this._onmouseup = function(e) {
		if (e.currentTarget.moved)
			return;

		if (e.currentTarget.component._onCarouselClickHandler != null
				&& e.currentTarget.component._onCarouselClickHandler != "undefined") {
			e.currentTarget.component._onCarouselClickHandler((-1)
					* (e.currentTarget.component.offset - 3));
			e.currentTarget.moved = true;
		}

		e.stopPropagation();
	};

	this._onmousedown = function(e) {
		e.preventDefault();
		e.currentTarget.moved = false;
		e.currentTarget.prevX = e.pageX;
		e.currentTarget.prevY = e.pageY;
	};

	this._onmousemove = function(e) {
		e.preventDefault();

		if (e.pageX - e.currentTarget.prevX < -20) {
			if (e.currentTarget.moved == false) {
				e.currentTarget.component.slide(-1);
				e.currentTarget.moved = true;
			}
		}

		if (e.pageX - e.currentTarget.prevX > 20) {
			if (e.currentTarget.moved == false) {
				e.currentTarget.component.slide(1);
				e.currentTarget.moved = true;
			}
		}
	};

	if (typeof this.element.ontouchend == "undefined") {
		this.element.addEventListener("mousedown", this._onmousedown, false);
		this.element.addEventListener("mousemove", this._onmousemove, false);
		this.element.addEventListener("mouseup", this._onmouseup, false);
	} else {
		this.element.addEventListener("touchstart", this._ontouchstart, false);
		this.element.addEventListener("touchmove", this._ontouchmove, false);
		this.element.addEventListener("touchend", this._ontouchend, false);
	}
}.inherits(Component);

Carousel3D.prototype.start = function(images) {
	if (images == null || images == "undefined")
		return;

	for ( var i = 0; i < images.length; i++) {
		var canvas = this.createCanvasElement(images[i]);
		var li = document.createElement("li");

		// var order = i + 3;
		var order = i;
		if (order <= 5) {
			li.className = "img coverflow" + order;
		} else {
			li.className = "img coverflow6";
		}

		li.appendChild(canvas);
		document.getElementById("coverflow").appendChild(li);
	}
};

/**
 * Carousel3D 컴퍼넌트에 아이템 이미지를 추가합니다.
 * 
 * @param aImageUrl
 */
Carousel3D.prototype.addCoverImage = function(aImageUrl) {
	this.coverflowCnt++;
	var aImage = document.createElement("img");
	aImage.src = aImageUrl;
	aImage.onload = this._onimageload;
	aImage.component = this;
};

Carousel3D.prototype._onimageload = function(e) {
	var canvas = e.currentTarget.component.createCanvasElement(e.currentTarget);

	var li = document.createElement("li");

	var i = document.getElementById("coverflow").getElementsByTagName("li").length;

	// var order = i + 3;
	var order = i;
	if (order <= 5) {
		li.className = "img coverflow" + order;
	} else {
		li.className = "img coverflow6";
	}

	li.appendChild(canvas);
	document.getElementById("coverflow").appendChild(li);

	if (e.target.component.moveCenterFlag
			&& e.target.component.coverflowCnt - 1 == i) {
		e.target.component._moveCenterDeffered();
		e.target.component.moveCenterFlag = false;
	}
};

Carousel3D.prototype.createCanvasElement = function(img) {
	var canvas = document.createElement('canvas');

	// canvas.setAttribute("class", "CarouselCanvas");
	canvas.setAttribute('width', this.side);
	canvas.setAttribute('height', this.side);
	var _height = img.naturalHeight;
	var _width = img.naturalWidth;
	var w = false; // width is shorter?
	if (_height > _width)
		w = true;
	var sx, sy, sw, sh;
	if (w) {
		var _side = _width;
		sx = 0;
		sy = _height / 2 - _side / 2;
	} else {
		var _side = _height;
		sx = _width / 2 - _side / 2;
		sy = 0;
	}
	sw = _side;
	sh = _side;

	var cx = canvas.getContext('2d');
	cx.beginPath();
	cx.moveTo(this.radius, 0);
	cx.lineTo(canvas.width - this.radius, 0);
	cx.quadraticCurveTo(canvas.width, 0, canvas.width, this.radius);
	cx.lineTo(canvas.width, canvas.height - this.radius);
	cx.quadraticCurveTo(canvas.width, canvas.height,
			canvas.width - this.radius, canvas.height);
	cx.lineTo(this.radius, canvas.height);
	cx.quadraticCurveTo(0, canvas.height, 0, canvas.height - this.radius);
	cx.lineTo(0, this.radius);
	cx.quadraticCurveTo(0, 0, this.radius, 0);
	cx.clip();
	cx.drawImage(img, sx, sy, sw, sh, 0, 0, this.side, this.side);

	return canvas;
};

Carousel3D.prototype.flip = function(component) {
	var flow = this.queue.shift();
	if (!flow) {
		this.flipping = false;
		return;
	}
	this.flipping = true;
	var _offset = this.display_offset + flow.vector;
	var speed = 0.05;
	if (this.queue.length < 4)
		speed = 0.1;
	if (this.queue.length < 2)
		speed = 0.2;
	var coverflows = document.getElementById("coverflow").getElementsByTagName(
			"li");
	for ( var i = 0; i < coverflows.length; i++) {
		var order = i + _offset;
		if (order <= 0) {
			coverflows[i].className = "img coverflow0";
		} else if (order > 5) {
			coverflows[i].className = "img coverflow6";
		} else {
			coverflows[i].className = "img coverflow" + (order);
		}
	}

	this.display_offset = _offset;
	setTimeout(component.flip(component), speed * 1000 + 30);
};

Carousel3D.prototype.slide = function(diff) {
	var _offset = this.offset + diff;
	var coverflows = document.getElementById("coverflow").getElementsByTagName(
			"li");
	if (coverflows.length >= 4) {
		if (_offset > 3 || _offset < ((coverflows.length - 4) * -1))
			return false;
	} else if (coverflows.length < 4) {
		if ((diff < 0 && _offset <= 3 - coverflows.length)
				|| (diff > 0 && _offset > 3))
			return false;
	}
	this.move(diff * -1);
	this.offset = _offset;
	if (!this.flipping)
		this.flip(this);
};

Carousel3D.prototype.move = function(diff) {
	var abs = diff < 0 ? diff * -1 : diff;
	var vector = 1;
	if (diff > 0)
		vector = -1;
	for ( var i = 0; i < abs; i++) {
		var flow = {
			'vector' : vector,
			'speed' : 0.03
		};
		this.queue.push(flow);
	}
	return true;
};

/**
 * Carousel3D 아이템이 클릭되었을 시 호출되는 함수를 등록합니다. 이 때 등록되는 함수의 인자로 클릭된 아이템의 인덱스값을 확인할 수
 * 있습니다. ex) function onclick(index) { log.log("selected index:" + index); }
 * 
 * @param onClick
 */
Carousel3D.prototype.setOnCarouselClickHandler = function(onClick) {
	this._onCarouselClickHandler = onClick;
};

Carousel3D.prototype.moveCenter = function() {
	var coverflowLength = document.getElementById("coverflow")
			.getElementsByTagName("li").length;

	if (coverflowLength == this.coverflowCnt) {
		this._moveCenterDeffered();
	} else {
		this.moveCenterFlag = true;
	}
};

Carousel3D.prototype._moveCenterDeffered = function() {
	var length = document.getElementById("coverflow")
			.getElementsByTagName("li").length;

	var diff = 0;
	if (length >= 8) {
		diff = parseInt((length - 7) / 2);
	} else if (length <= 4) {
		diff = parseInt((7 - length) / 2);
	}
	this.slide(diff);
};

/**
 * Tweener는 component 객체에 animation을 적용하는 엔진입니다. Flash의 TweenMax 엔진과 동일한 인터페이스를
 * 사용했습니다.
 * 
 * @constructor
 */
var Tweener = function Tweener() {
};

/**
 * 인자가 되는 컴포넌트에 에니메이션을 적용하는 메소드입니다. ex)
 * Tweener.to(panel1,{x:0,y:0,scaleX:0.5,scaleY:0.5,color:0x00ff00},2,{ease:'ease-in-out',
 * onComplete:onCompleteHandler});
 * 
 * @param obj
 *            에니메이션이 적용될 컴포는트 레퍼런스
 * @param props
 *            에니메이션이 적용될 속성값들. 다중 속성 설정 가능. x, y, scaleX, scaleY, rotation,
 *            color, alpha
 * @param duration
 *            에니메이션이 수행될 시간. 초단위
 * @param options
 *            에니메이션 수행에 관련된 옵션 값. onComplete, delay, ease (ease-in, ease-out,
 *            ease-in-out, ease-out-in, cubic-bezier..., default, linear)
 */
Tweener.to = function to(obj, props, duration, options) {
	if (options == null)
		options = {};
	if (options.ease != undefined)
		obj.setStyle('webkitTransitionTimingFunction', options.ease);
	else
		obj.setStyle('webkitTransitionTimingFunction', 'linear');

	if (obj.onWebkitTransitionEnd != undefined)
		obj.removeEventListener("webkitTransitionEnd",
				obj.onWebkitTransitionEnd);
	if (options.onComplete != undefined) {
		obj.addEventListener("webkitTransitionEnd", options.onComplete);
		obj.onWebkitTransitionEnd = options.onComplete;
	}

	obj.setStyle('webkitTransitionDelay', 0 + 's');
	if (options.delay != undefined)
		obj.setStyle('webkitTransitionDelay', options.delay + 's');

	obj.setStyle('webkitTransitionProperty', 'all');
	obj.setStyle('webkitTransitionDuration', duration + 's');

	if (props.alpha != undefined)
		obj.setStyle('opacity', props.alpha);
	if (props.color != undefined)
		obj.setStyle('background-color', props.color);

	/*
	 * //thess codes were more advanced but android was not be able to handle
	 * matrix properly. var matrix = new
	 * WebKitCSSMatrix(obj.element.style.webkitTransform); //strange...can't get
	 * webkitTransform using getComputedStyle function..
	 * if(props.rotation!=undefined)matrix.rotate(0,0,props.rotation);
	 * 
	 * if(props.x!=undefined)matrix.m41=props.x;
	 * if(props.y!=undefined)matrix.m42=props.y;
	 * 
	 * if(props.scaleX!=undefined)matrix.m11=props.scaleX;
	 * if(props.scaleY!=undefined)matrix.m22=props.scaleY;
	 */
	var transformStr = '';

	if (props.x != undefined)
		transformStr += 'translateX(' + props.x + 'px) ';
	if (props.y != undefined)
		transformStr += 'translateY(' + props.y + 'px) ';
	if (props.scaleX != undefined)
		transformStr += 'scaleX(' + props.scaleX + ') ';
	if (props.scaleY != undefined)
		transformStr += 'scaleY(' + props.scaleY + ') ';
	if (props.rotation != undefined)
		transformStr += 'rotate(' + props.rotation + 'deg) ';

	obj.setStyle('webkitTransform', transformStr);
};

/*******************************************************************************
 * ARButton
 ******************************************************************************/
var ARButton = function ARButton() {
	this.parent(id, dataMapId);
}.inherits(BaseButtonComponent);

/*******************************************************************************
 * QRButton
 ******************************************************************************/
var QRButton = function QRButton() {
	this.parent(id, dataMapId);
}.inherits(BaseButtonComponent);

/*******************************************************************************
 * FacebookButton
 ******************************************************************************/

var FacebookButton = function FacebookButton(id, dataMapId) {
	this.parent(id, dataMapId);
	this.msg = "";
	this.link = "";
	this.name = "";
	this.caption = "";
	this.desc = "";

	this._onclick = function() {
		sns.loadFacebookLogin(this.msg, this.link, this.name, this.caption,
				this.desc);
	};
	this.setOnClickHandler(this._onclick);
}.inherits(BaseButtonComponent);

FacebookButton.prototype.setSetting = function(msg, link, name, caption, desc) {
	if (msg != undefined && msg != null)
		this.msg = msg;
	if (link != undefined && link != null)
		this.link = link;
	if (name != undefined && name != null)
		this.name = name;
	if (caption != undefined && caption != null)
		this.caption = caption;
	if (desc != undefined && desc != null)
		this.desc = desc;
};

/*******************************************************************************
 * TwitterButton
 ******************************************************************************/

var TwitterButton = function TwitterButton(id, dataMapId) {
	if (id == undefined)
		return;
	this.parent(id, dataMapId);
	this.msg = "";
	this.url = "";

	this._onclick = function() {
		sns.loadTwitterLogin(this.msg, this.url);
	};
	this.setOnClickHandler(this._onclick);

}.inherits(BaseButtonComponent);

TwitterButton.prototype.setSetting = function(msg, url) {
	if (msg != undefined && msg != null)
		this.msg = msg;
	if (url != undefined && url != null)
		this.url = url;
};

/*******************************************************************************
 * Util
 ******************************************************************************/

function getElementsByClassName(node, classname) {
	if (node.getElementsByClassName) { // use native implementation if
		// available
		return node.getElementsByClassName(classname);
	} else {
		return (function getElementsByClass(searchClass, node) {
			if (node == null)
				node = document;
			var classElements = [], els = node.getElementsByTagName("*"), elsLen = els.length, pattern = new RegExp(
					"(^|\\s)" + searchClass + "(\\s|$)"), i, j;

			for (i = 0, j = 0; i < elsLen; i++) {
				if (pattern.test(els[i].className)) {
					classElements[j] = els[i];
					j++;
				}
			}
			return classElements;
		})(classname, node);
	}
}

function getStyle(element, prop) {
	var y = "";

	if (element.currentStyle)
		y = element.currentStyle[prop];
	else if (window.getComputedStyle)
		y = document.defaultView.getComputedStyle(element, null)
				.getPropertyValue(prop);
	return y;
};

/*******************************************************************************
 * Formatters *
 ******************************************************************************/
function NumberFormatter() {
}

NumberFormatter.prototype.format = function format(data) {
	var returnValue = data;

	data += "";
	x = data.split(".");
	x1 = x[0];
	x2 = x.length > 1 ? "." + x[1] : "";

	var rgx = /(\d+)(\d{3})/;

	while (rgx.test(x1))
		x1 = x1.replace(rgx, "$1" + "," + "$2");

	returnValue = x1 + x2;

	return returnValue;
};

// example should be like YYYY/MM/DD, YYYY.MM.DD, MM/DD/YYYY, or YY-MM-DD
function DateFormatter(example) {
	this.example = example;
	this.delimiter = "";

	for ( var i = 0; i < example.length; i++) {
		var char = example.charAt(i);

		if (char != "Y" && char != "M" && char != "D") {
			this.delimiter = char;

			break;
		}
	}

	this.formatType = example.split(this.delimiter);
}

// Data format is YYYYMMDD
DateFormatter.prototype.format = function format(data) {
	var returnValue = "";

	for ( var i = 0; i < 3; i++) {
		if (this.formatType[i].charAt(0) == "Y") {
			if (this.formatType[i].length == 2)
				returnValue += data.substring(2, this.formatType[i].length + 2);
			else
				returnValue += data.substring(0, this.formatType[i].length);

			if (i != 2)
				returnValue += this.delimiter;
		} else if (this.formatType[i].charAt(0) == "M") {
			returnValue += data.substring(4, 6);

			if (i != 2)
				returnValue += this.delimiter;
		} else if (this.formatType[i].charAt(0) == "D") {
			returnValue += data.substring(6, 8);

			if (i != 2)
				returnValue += this.delimiter;
		}
	}

	return returnValue;
};

function HiddenFormatter(startPosition, length, markChar) {
	this.startPosition = startPosition;
	this.length = length;
	this.markChar = markChar;
}

HiddenFormatter.prototype.format = function format(data) {
	var returnValue = "";

	if (data != undefined && data != "") {
		var replacedStr = data.substr(this.startPosition, this.length);

		returnValue = data.split(replacedStr)[0];

		for ( var i = 0; i < this.length; i++)
			returnValue += this.markChar;

		returnValue += data.split(replacedStr)[1];
	}

	return returnValue;
};

function CurrencyFormatter(isFront, currencyChar, useDecimal) {
	this.isFront = isFront;
	this.currencyChar = currencyChar;
	this.useDecimal = useDecimal;
}

CurrencyFormatter.prototype.format = function format(data) {
	var returnValue = data;

	data += "";
	x = data.split(".");
	x1 = x[0];
	x2 = x.length > 1 ? "." + x[1] : "";

	var rgx = /(\d+)(\d{3})/;

	while (rgx.test(x1))
		x1 = x1.replace(rgx, "$1" + "," + "$2");

	returnValue = x1 + (this.useDecimal == true ? x2 : "");

	if (this.isFront)
		returnValue = this.currencyChar + returnValue;
	else
		returnValue = returnValue + this.currencyChar;

	return returnValue;
};

function EllipsisFormatter(maxLength, ellipsisStr) {
	this.maxLength = maxLength;
	this.ellipsisStr = (ellipsisStr == undefined) ? "..." : ellipsisStr;
}

EllipsisFormatter.prototype.format = function format(data) {
	if (data.length > this.maxLength) {
		var returnValue = data.substring(0, this.maxLength
				- this.ellipsisStr.length)
				+ this.ellipsisStr;

		return returnValue;
	} else
		return data;
};

function UnitFormatter(isFront, unit) {
	this.isFront = isFront;
	this.unit = unit;
}

UnitFormatter.prototype.format = function format(data) {
	var returnValue = data;

	if (this.isFront)
		returnValue = this.unit + returnValue;
	else
		returnValue = returnValue + this.unit;

	return returnValue;
};

function CustomFormatter(customFunction) {
	this.customFunction = customFunction;
}

CustomFormatter.prototype.format = function format(data) {
	return this.customFunction(data);
};